#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>

#include "../../Port.h"
#include "../../NLS.h"
#include "../GB.h"
#include "../gbCheats.h"
#include "../gbGlobals.h"
#include "../gbMemory.h"
#include "../gbSGB.h"
#include "../gbSound.h"
#include "../../common/Util.h"
#include "../../common/System.h"
#include "../../common/SystemGlobals.h"
#include "../../common/movie.h"
#include "../../common/vbalua.h"

// FIXME: constant (GB) or boolean (GBA)?!
enum
{
	C_FLAG = 0x10,
	H_FLAG = 0x20,
	N_FLAG = 0x40,
	Z_FLAG = 0x80
};

bool gbUpdateSizes();
bool inBios = false;

// debugging
bool memorydebug = false;
char gbBuffer[2048];

extern u16 gbLineMix[160];

// mappers
void (*mapper)(u16, u8)		= NULL;
void (*mapperRAM)(u16, u8)	= NULL;
u8	 (*mapperReadRAM)(u16)	= NULL;
void (*mapperUpdateClock)() = NULL;

// registers
gbRegister PC;
gbRegister SP;
gbRegister AF;
gbRegister BC;
gbRegister DE;
gbRegister HL;
u16		   IFF = 0;
// 0xff04
u8 register_DIV = 0;
// 0xff05
u8 register_TIMA = 0;
// 0xff06
u8 register_TMA = 0;
// 0xff07
u8 register_TAC = 0;
// 0xff0f
u8 register_IF = 0;
// 0xff40
u8 register_LCDC = 0;
// 0xff41
u8 register_STAT = 0;
// 0xff42
u8 register_SCY = 0;
// 0xff43
u8 register_SCX = 0;
// 0xff44
u8 register_LY = 0;
// 0xff45
u8 register_LYC = 0;
// 0xff46
u8 register_DMA = 0;
// 0xff4a
u8 register_WY = 0;
// 0xff4b
u8 register_WX = 0;
// 0xff4f
u8 register_VBK = 0;
// 0xff51
u8 register_HDMA1 = 0;
// 0xff52
u8 register_HDMA2 = 0;
// 0xff53
u8 register_HDMA3 = 0;
// 0xff54
u8 register_HDMA4 = 0;
// 0xff55
u8 register_HDMA5 = 0;
// 0xff70
u8 register_SVBK = 0;
// 0xffff
u8 register_IE = 0;

// ticks definition
int32 GBDIV_CLOCK_TICKS = 64;
int32 GBLCD_MODE_0_CLOCK_TICKS	 = 51;
int32 GBLCD_MODE_1_CLOCK_TICKS	 = 1140;
int32 GBLCD_MODE_2_CLOCK_TICKS	 = 20;
int32 GBLCD_MODE_3_CLOCK_TICKS	 = 43;
int32 GBLY_INCREMENT_CLOCK_TICKS = 114;
int32 GBTIMER_MODE_0_CLOCK_TICKS = 256;
int32 GBTIMER_MODE_1_CLOCK_TICKS = 4;
int32 GBTIMER_MODE_2_CLOCK_TICKS = 16;
int32 GBTIMER_MODE_3_CLOCK_TICKS = 64;
int32 GBSERIAL_CLOCK_TICKS		 = 128;
int32 GBSYNCHRONIZE_CLOCK_TICKS	 = 52920;

// state variables
// general
int32 gbClockTicks = 0;
bool8 gbSystemMessage		= false;
int32 gbGBCColorType		= 0;
int32 gbRomType				= 0;
int32 gbRemainingClockTicks = 0;
int32 gbOldClockTicks		= 0;
int32 gbIntBreak			= 0;
int32 gbInterruptLaunched	= 0;
u8	 gbCheatingDevice		= 0; // 1 = GS, 2 = GG
// breakpoint
bool8 breakpoint = false;
// interrupt
int32 gbInt48Signal	= 0;
int32 gbInterruptWait = 0;
// serial
int32 gbSerialOn	= 0;
int32 gbSerialTicks = 0;
int32 gbSerialBits	= 0;
// timer
int32 gbTimerOn			= false;
int32 gbTimerTicks		= GBTIMER_MODE_0_CLOCK_TICKS;
int32 gbTimerClockTicks = GBTIMER_MODE_0_CLOCK_TICKS;
int32 gbTimerMode		= 0;
bool8 gbIncreased		= false;
// The internal timer is always active, and it is
// not reset by writing to register_TIMA/TMA, but by
// writing to register_DIV...
int32 gbInternalTimer	= 0x55;
const u8 gbTimerMask[4] = { 0xff, 0x3, 0xf, 0x3f };
const u8 gbTimerBug[8]	= { 0x80, 0x80, 0x02, 0x02, 0x0, 0xff, 0x0, 0xff };
int32 gbTimerModeChange = false;
int32 gbTimerOnChange	= false;
// lcd
bool  gbScreenOn		= true;

int32 gbLcdMode			= 2;
int32 gbLcdModeDelayed	= 2;
int32 gbLcdTicks		= GBLCD_MODE_2_CLOCK_TICKS - 1;
int32 gbLcdTicksDelayed = GBLCD_MODE_2_CLOCK_TICKS;
int32 gbLcdLYIncrementTicks		   = 114;
int32 gbLcdLYIncrementTicksDelayed = 115;
int32 gbScreenTicks				   = 0;
u8	  gbSCYLine[300];
u8	  gbSCXLine[300];
u8	  gbBgpLine[300];
u8	  gbObp0Line[300];
u8	  gbObp1Line[300];
u8	  gbSpritesTicks[300];
int32 gbLYChangeHappened	= false;
int32 gbLCDChangeHappened	= false;
int32 gbLine99Ticks			= 1;
int32 gbRegisterLYLCDCOffOn = 0;
int32 inUseRegister_WX		= 0;
int32 inUseRegister_WY		= 0;

// Used to keep track of the line that ellapse
// when screen is off
int32 gbWhiteScreen		= 0;
bool8 gbBlackScreen		= false;
int32 register_LCDCBusy = 0;

// div
int32 gbDivTicks = GBDIV_CLOCK_TICKS;
// cgb
int32 gbVramBank		= 0;
int32 gbWramBank		= 1;
//sgb
bool8 gbSgbResetFlag = false;
// gbHdmaDestination is 0x99d0 on startup (tested on HW)
// but I'm not sure what gbHdmaSource is...
int32 gbHdmaSource		= 0x99d0;
int32 gbHdmaDestination = 0x99d0;
int32 gbHdmaBytes		= 0x0000;
int32 gbHdmaOn			= 0;
int32 gbSpeed			= 0;
// timing
u32	  gbElapsedTime		 = 0;
u32	  gbTimeNow			 = 0;
int32 gbSynchronizeTicks = GBSYNCHRONIZE_CLOCK_TICKS;
// emulator features
int32 gbBattery		 = 0;
bool8 gbBatteryError = false;
int32 gbJoymask[4]	 = { 0, 0, 0, 0 };

// HACK
static int32 stopCounter = 0; // this has to be saved

u8 gbRamFill = 0xff;

int32 gbRomSizes[] = { 0x00008000, // 32K
	                 0x00010000, // 64K
	                 0x00020000, // 128K
	                 0x00040000, // 256K
	                 0x00080000, // 512K
	                 0x00100000, // 1024K
	                 0x00200000, // 2048K
	                 0x00400000, // 4096K
	                 0x00800000  // 8192K
};
int32 gbRomSizesMasks[] = { 0x00007fff,
	                      0x0000ffff,
	                      0x0001ffff,
	                      0x0003ffff,
	                      0x0007ffff,
	                      0x000fffff,
	                      0x001fffff,
	                      0x003fffff,
	                      0x007fffff };

int32 gbRamSizes[6] = { 0x00000000, // 0K
	                  0x00002000, // 2K  // Changed to 2000 to avoid problems with gbMemoryMap...
	                  0x00002000, // 8K
	                  0x00008000, // 32K
	                  0x00020000, // 128K
	                  0x00010000  // 64K
};

int32 gbRamSizesMasks[6] = { 0x00000000,
	                       0x000007ff,
	                       0x00001fff,
	                       0x00007fff,
	                       0x0001ffff,
	                       0x0000ffff };

int32 gbCycles[] = {
//  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
	1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1,  // 0
	1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1,  // 1
	2, 3, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 1,  // 2
	2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 2, 2, 1, 1, 2, 1,  // 3
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // 4
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // 5
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // 6
	2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1,  // 7
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // 8
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // 9
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // a
	1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1,  // b
	2, 3, 3, 4, 3, 4, 2, 4, 2, 4, 3, 2, 3, 6, 2, 4,  // c
	2, 3, 3, 1, 3, 4, 2, 4, 2, 4, 3, 1, 3, 1, 2, 4,  // d
	3, 3, 2, 1, 1, 4, 2, 4, 4, 1, 4, 1, 1, 1, 2, 4,  // e
	3, 3, 2, 1, 1, 4, 2, 4, 3, 2, 4, 1, 0, 1, 2, 4   // f
};

int32 gbCyclesCB[] = {
//  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 0
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 1
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 2
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 3
	2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2,  // 4
	2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2,  // 5
	2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2,  // 6
	2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2,  // 7
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 8
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // 9
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // a
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // b
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // c
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // d
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2,  // e
	2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2   // f
};

u16 DAATable[] = {
	0x0080, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700,
	0x0800, 0x0900, 0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500,
	0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500, 0x1600, 0x1700,
	0x1800, 0x1900, 0x2000, 0x2100, 0x2200, 0x2300, 0x2400, 0x2500,
	0x2000, 0x2100, 0x2200, 0x2300, 0x2400, 0x2500, 0x2600, 0x2700,
	0x2800, 0x2900, 0x3000, 0x3100, 0x3200, 0x3300, 0x3400, 0x3500,
	0x3000, 0x3100, 0x3200, 0x3300, 0x3400, 0x3500, 0x3600, 0x3700,
	0x3800, 0x3900, 0x4000, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500,
	0x4000, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500, 0x4600, 0x4700,
	0x4800, 0x4900, 0x5000, 0x5100, 0x5200, 0x5300, 0x5400, 0x5500,
	0x5000, 0x5100, 0x5200, 0x5300, 0x5400, 0x5500, 0x5600, 0x5700,
	0x5800, 0x5900, 0x6000, 0x6100, 0x6200, 0x6300, 0x6400, 0x6500,
	0x6000, 0x6100, 0x6200, 0x6300, 0x6400, 0x6500, 0x6600, 0x6700,
	0x6800, 0x6900, 0x7000, 0x7100, 0x7200, 0x7300, 0x7400, 0x7500,
	0x7000, 0x7100, 0x7200, 0x7300, 0x7400, 0x7500, 0x7600, 0x7700,
	0x7800, 0x7900, 0x8000, 0x8100, 0x8200, 0x8300, 0x8400, 0x8500,
	0x8000, 0x8100, 0x8200, 0x8300, 0x8400, 0x8500, 0x8600, 0x8700,
	0x8800, 0x8900, 0x9000, 0x9100, 0x9200, 0x9300, 0x9400, 0x9500,
	0x9000, 0x9100, 0x9200, 0x9300, 0x9400, 0x9500, 0x9600, 0x9700,
	0x9800, 0x9900, 0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510,
	0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510, 0x0610, 0x0710,
	0x0810, 0x0910, 0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510,
	0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510, 0x1610, 0x1710,
	0x1810, 0x1910, 0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510,
	0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510, 0x2610, 0x2710,
	0x2810, 0x2910, 0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510,
	0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510, 0x3610, 0x3710,
	0x3810, 0x3910, 0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510,
	0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510, 0x4610, 0x4710,
	0x4810, 0x4910, 0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510,
	0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510, 0x5610, 0x5710,
	0x5810, 0x5910, 0x6010, 0x6110, 0x6210, 0x6310, 0x6410, 0x6510,
	0x6010, 0x6110, 0x6210, 0x6310, 0x6410, 0x6510, 0x6610, 0x6710,
	0x6810, 0x6910, 0x7010, 0x7110, 0x7210, 0x7310, 0x7410, 0x7510,
	0x7010, 0x7110, 0x7210, 0x7310, 0x7410, 0x7510, 0x7610, 0x7710,
	0x7810, 0x7910, 0x8010, 0x8110, 0x8210, 0x8310, 0x8410, 0x8510,
	0x8010, 0x8110, 0x8210, 0x8310, 0x8410, 0x8510, 0x8610, 0x8710,
	0x8810, 0x8910, 0x9010, 0x9110, 0x9210, 0x9310, 0x9410, 0x9510,
	0x9010, 0x9110, 0x9210, 0x9310, 0x9410, 0x9510, 0x9610, 0x9710,
	0x9810, 0x9910, 0xA010, 0xA110, 0xA210, 0xA310, 0xA410, 0xA510,
	0xA010, 0xA110, 0xA210, 0xA310, 0xA410, 0xA510, 0xA610, 0xA710,
	0xA810, 0xA910, 0xB010, 0xB110, 0xB210, 0xB310, 0xB410, 0xB510,
	0xB010, 0xB110, 0xB210, 0xB310, 0xB410, 0xB510, 0xB610, 0xB710,
	0xB810, 0xB910, 0xC010, 0xC110, 0xC210, 0xC310, 0xC410, 0xC510,
	0xC010, 0xC110, 0xC210, 0xC310, 0xC410, 0xC510, 0xC610, 0xC710,
	0xC810, 0xC910, 0xD010, 0xD110, 0xD210, 0xD310, 0xD410, 0xD510,
	0xD010, 0xD110, 0xD210, 0xD310, 0xD410, 0xD510, 0xD610, 0xD710,
	0xD810, 0xD910, 0xE010, 0xE110, 0xE210, 0xE310, 0xE410, 0xE510,
	0xE010, 0xE110, 0xE210, 0xE310, 0xE410, 0xE510, 0xE610, 0xE710,
	0xE810, 0xE910, 0xF010, 0xF110, 0xF210, 0xF310, 0xF410, 0xF510,
	0xF010, 0xF110, 0xF210, 0xF310, 0xF410, 0xF510, 0xF610, 0xF710,
	0xF810, 0xF910, 0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510,
	0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510, 0x0610, 0x0710,
	0x0810, 0x0910, 0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510,
	0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510, 0x1610, 0x1710,
	0x1810, 0x1910, 0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510,
	0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510, 0x2610, 0x2710,
	0x2810, 0x2910, 0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510,
	0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510, 0x3610, 0x3710,
	0x3810, 0x3910, 0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510,
	0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510, 0x4610, 0x4710,
	0x4810, 0x4910, 0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510,
	0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510, 0x5610, 0x5710,
	0x5810, 0x5910, 0x6010, 0x6110, 0x6210, 0x6310, 0x6410, 0x6510,
	0x0600, 0x0700, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00,
	0x0E00, 0x0F00, 0x1000, 0x1100, 0x1200, 0x1300, 0x1400, 0x1500,
	0x1600, 0x1700, 0x1800, 0x1900, 0x1A00, 0x1B00, 0x1C00, 0x1D00,
	0x1E00, 0x1F00, 0x2000, 0x2100, 0x2200, 0x2300, 0x2400, 0x2500,
	0x2600, 0x2700, 0x2800, 0x2900, 0x2A00, 0x2B00, 0x2C00, 0x2D00,
	0x2E00, 0x2F00, 0x3000, 0x3100, 0x3200, 0x3300, 0x3400, 0x3500,
	0x3600, 0x3700, 0x3800, 0x3900, 0x3A00, 0x3B00, 0x3C00, 0x3D00,
	0x3E00, 0x3F00, 0x4000, 0x4100, 0x4200, 0x4300, 0x4400, 0x4500,
	0x4600, 0x4700, 0x4800, 0x4900, 0x4A00, 0x4B00, 0x4C00, 0x4D00,
	0x4E00, 0x4F00, 0x5000, 0x5100, 0x5200, 0x5300, 0x5400, 0x5500,
	0x5600, 0x5700, 0x5800, 0x5900, 0x5A00, 0x5B00, 0x5C00, 0x5D00,
	0x5E00, 0x5F00, 0x6000, 0x6100, 0x6200, 0x6300, 0x6400, 0x6500,
	0x6600, 0x6700, 0x6800, 0x6900, 0x6A00, 0x6B00, 0x6C00, 0x6D00,
	0x6E00, 0x6F00, 0x7000, 0x7100, 0x7200, 0x7300, 0x7400, 0x7500,
	0x7600, 0x7700, 0x7800, 0x7900, 0x7A00, 0x7B00, 0x7C00, 0x7D00,
	0x7E00, 0x7F00, 0x8000, 0x8100, 0x8200, 0x8300, 0x8400, 0x8500,
	0x8600, 0x8700, 0x8800, 0x8900, 0x8A00, 0x8B00, 0x8C00, 0x8D00,
	0x8E00, 0x8F00, 0x9000, 0x9100, 0x9200, 0x9300, 0x9400, 0x9500,
	0x9600, 0x9700, 0x9800, 0x9900, 0x9A00, 0x9B00, 0x9C00, 0x9D00,
	0x9E00, 0x9F00, 0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510,
	0x0610, 0x0710, 0x0810, 0x0910, 0x0A10, 0x0B10, 0x0C10, 0x0D10,
	0x0E10, 0x0F10, 0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510,
	0x1610, 0x1710, 0x1810, 0x1910, 0x1A10, 0x1B10, 0x1C10, 0x1D10,
	0x1E10, 0x1F10, 0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510,
	0x2610, 0x2710, 0x2810, 0x2910, 0x2A10, 0x2B10, 0x2C10, 0x2D10,
	0x2E10, 0x2F10, 0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510,
	0x3610, 0x3710, 0x3810, 0x3910, 0x3A10, 0x3B10, 0x3C10, 0x3D10,
	0x3E10, 0x3F10, 0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510,
	0x4610, 0x4710, 0x4810, 0x4910, 0x4A10, 0x4B10, 0x4C10, 0x4D10,
	0x4E10, 0x4F10, 0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510,
	0x5610, 0x5710, 0x5810, 0x5910, 0x5A10, 0x5B10, 0x5C10, 0x5D10,
	0x5E10, 0x5F10, 0x6010, 0x6110, 0x6210, 0x6310, 0x6410, 0x6510,
	0x6610, 0x6710, 0x6810, 0x6910, 0x6A10, 0x6B10, 0x6C10, 0x6D10,
	0x6E10, 0x6F10, 0x7010, 0x7110, 0x7210, 0x7310, 0x7410, 0x7510,
	0x7610, 0x7710, 0x7810, 0x7910, 0x7A10, 0x7B10, 0x7C10, 0x7D10,
	0x7E10, 0x7F10, 0x8010, 0x8110, 0x8210, 0x8310, 0x8410, 0x8510,
	0x8610, 0x8710, 0x8810, 0x8910, 0x8A10, 0x8B10, 0x8C10, 0x8D10,
	0x8E10, 0x8F10, 0x9010, 0x9110, 0x9210, 0x9310, 0x9410, 0x9510,
	0x9610, 0x9710, 0x9810, 0x9910, 0x9A10, 0x9B10, 0x9C10, 0x9D10,
	0x9E10, 0x9F10, 0xA010, 0xA110, 0xA210, 0xA310, 0xA410, 0xA510,
	0xA610, 0xA710, 0xA810, 0xA910, 0xAA10, 0xAB10, 0xAC10, 0xAD10,
	0xAE10, 0xAF10, 0xB010, 0xB110, 0xB210, 0xB310, 0xB410, 0xB510,
	0xB610, 0xB710, 0xB810, 0xB910, 0xBA10, 0xBB10, 0xBC10, 0xBD10,
	0xBE10, 0xBF10, 0xC010, 0xC110, 0xC210, 0xC310, 0xC410, 0xC510,
	0xC610, 0xC710, 0xC810, 0xC910, 0xCA10, 0xCB10, 0xCC10, 0xCD10,
	0xCE10, 0xCF10, 0xD010, 0xD110, 0xD210, 0xD310, 0xD410, 0xD510,
	0xD610, 0xD710, 0xD810, 0xD910, 0xDA10, 0xDB10, 0xDC10, 0xDD10,
	0xDE10, 0xDF10, 0xE010, 0xE110, 0xE210, 0xE310, 0xE410, 0xE510,
	0xE610, 0xE710, 0xE810, 0xE910, 0xEA10, 0xEB10, 0xEC10, 0xED10,
	0xEE10, 0xEF10, 0xF010, 0xF110, 0xF210, 0xF310, 0xF410, 0xF510,
	0xF610, 0xF710, 0xF810, 0xF910, 0xFA10, 0xFB10, 0xFC10, 0xFD10,
	0xFE10, 0xFF10, 0x0090, 0x0110, 0x0210, 0x0310, 0x0410, 0x0510,
	0x0610, 0x0710, 0x0810, 0x0910, 0x0A10, 0x0B10, 0x0C10, 0x0D10,
	0x0E10, 0x0F10, 0x1010, 0x1110, 0x1210, 0x1310, 0x1410, 0x1510,
	0x1610, 0x1710, 0x1810, 0x1910, 0x1A10, 0x1B10, 0x1C10, 0x1D10,
	0x1E10, 0x1F10, 0x2010, 0x2110, 0x2210, 0x2310, 0x2410, 0x2510,
	0x2610, 0x2710, 0x2810, 0x2910, 0x2A10, 0x2B10, 0x2C10, 0x2D10,
	0x2E10, 0x2F10, 0x3010, 0x3110, 0x3210, 0x3310, 0x3410, 0x3510,
	0x3610, 0x3710, 0x3810, 0x3910, 0x3A10, 0x3B10, 0x3C10, 0x3D10,
	0x3E10, 0x3F10, 0x4010, 0x4110, 0x4210, 0x4310, 0x4410, 0x4510,
	0x4610, 0x4710, 0x4810, 0x4910, 0x4A10, 0x4B10, 0x4C10, 0x4D10,
	0x4E10, 0x4F10, 0x5010, 0x5110, 0x5210, 0x5310, 0x5410, 0x5510,
	0x5610, 0x5710, 0x5810, 0x5910, 0x5A10, 0x5B10, 0x5C10, 0x5D10,
	0x5E10, 0x5F10, 0x6010, 0x6110, 0x6210, 0x6310, 0x6410, 0x6510,
	0x00C0, 0x0140, 0x0240, 0x0340, 0x0440, 0x0540, 0x0640, 0x0740,
	0x0840, 0x0940, 0x0A40, 0x0B40, 0x0C40, 0x0D40, 0x0E40, 0x0F40,
	0x1040, 0x1140, 0x1240, 0x1340, 0x1440, 0x1540, 0x1640, 0x1740,
	0x1840, 0x1940, 0x1A40, 0x1B40, 0x1C40, 0x1D40, 0x1E40, 0x1F40,
	0x2040, 0x2140, 0x2240, 0x2340, 0x2440, 0x2540, 0x2640, 0x2740,
	0x2840, 0x2940, 0x2A40, 0x2B40, 0x2C40, 0x2D40, 0x2E40, 0x2F40,
	0x3040, 0x3140, 0x3240, 0x3340, 0x3440, 0x3540, 0x3640, 0x3740,
	0x3840, 0x3940, 0x3A40, 0x3B40, 0x3C40, 0x3D40, 0x3E40, 0x3F40,
	0x4040, 0x4140, 0x4240, 0x4340, 0x4440, 0x4540, 0x4640, 0x4740,
	0x4840, 0x4940, 0x4A40, 0x4B40, 0x4C40, 0x4D40, 0x4E40, 0x4F40,
	0x5040, 0x5140, 0x5240, 0x5340, 0x5440, 0x5540, 0x5640, 0x5740,
	0x5840, 0x5940, 0x5A40, 0x5B40, 0x5C40, 0x5D40, 0x5E40, 0x5F40,
	0x6040, 0x6140, 0x6240, 0x6340, 0x6440, 0x6540, 0x6640, 0x6740,
	0x6840, 0x6940, 0x6A40, 0x6B40, 0x6C40, 0x6D40, 0x6E40, 0x6F40,
	0x7040, 0x7140, 0x7240, 0x7340, 0x7440, 0x7540, 0x7640, 0x7740,
	0x7840, 0x7940, 0x7A40, 0x7B40, 0x7C40, 0x7D40, 0x7E40, 0x7F40,
	0x8040, 0x8140, 0x8240, 0x8340, 0x8440, 0x8540, 0x8640, 0x8740,
	0x8840, 0x8940, 0x8A40, 0x8B40, 0x8C40, 0x8D40, 0x8E40, 0x8F40,
	0x9040, 0x9140, 0x9240, 0x9340, 0x9440, 0x9540, 0x9640, 0x9740,
	0x9840, 0x9940, 0x9A40, 0x9B40, 0x9C40, 0x9D40, 0x9E40, 0x9F40,
	0xA040, 0xA140, 0xA240, 0xA340, 0xA440, 0xA540, 0xA640, 0xA740,
	0xA840, 0xA940, 0xAA40, 0xAB40, 0xAC40, 0xAD40, 0xAE40, 0xAF40,
	0xB040, 0xB140, 0xB240, 0xB340, 0xB440, 0xB540, 0xB640, 0xB740,
	0xB840, 0xB940, 0xBA40, 0xBB40, 0xBC40, 0xBD40, 0xBE40, 0xBF40,
	0xC040, 0xC140, 0xC240, 0xC340, 0xC440, 0xC540, 0xC640, 0xC740,
	0xC840, 0xC940, 0xCA40, 0xCB40, 0xCC40, 0xCD40, 0xCE40, 0xCF40,
	0xD040, 0xD140, 0xD240, 0xD340, 0xD440, 0xD540, 0xD640, 0xD740,
	0xD840, 0xD940, 0xDA40, 0xDB40, 0xDC40, 0xDD40, 0xDE40, 0xDF40,
	0xE040, 0xE140, 0xE240, 0xE340, 0xE440, 0xE540, 0xE640, 0xE740,
	0xE840, 0xE940, 0xEA40, 0xEB40, 0xEC40, 0xED40, 0xEE40, 0xEF40,
	0xF040, 0xF140, 0xF240, 0xF340, 0xF440, 0xF540, 0xF640, 0xF740,
	0xF840, 0xF940, 0xFA40, 0xFB40, 0xFC40, 0xFD40, 0xFE40, 0xFF40,
	0xA050, 0xA150, 0xA250, 0xA350, 0xA450, 0xA550, 0xA650, 0xA750,
	0xA850, 0xA950, 0xAA50, 0xAB50, 0xAC50, 0xAD50, 0xAE50, 0xAF50,
	0xB050, 0xB150, 0xB250, 0xB350, 0xB450, 0xB550, 0xB650, 0xB750,
	0xB850, 0xB950, 0xBA50, 0xBB50, 0xBC50, 0xBD50, 0xBE50, 0xBF50,
	0xC050, 0xC150, 0xC250, 0xC350, 0xC450, 0xC550, 0xC650, 0xC750,
	0xC850, 0xC950, 0xCA50, 0xCB50, 0xCC50, 0xCD50, 0xCE50, 0xCF50,
	0xD050, 0xD150, 0xD250, 0xD350, 0xD450, 0xD550, 0xD650, 0xD750,
	0xD850, 0xD950, 0xDA50, 0xDB50, 0xDC50, 0xDD50, 0xDE50, 0xDF50,
	0xE050, 0xE150, 0xE250, 0xE350, 0xE450, 0xE550, 0xE650, 0xE750,
	0xE850, 0xE950, 0xEA50, 0xEB50, 0xEC50, 0xED50, 0xEE50, 0xEF50,
	0xF050, 0xF150, 0xF250, 0xF350, 0xF450, 0xF550, 0xF650, 0xF750,
	0xF850, 0xF950, 0xFA50, 0xFB50, 0xFC50, 0xFD50, 0xFE50, 0xFF50,
	0x00D0, 0x0150, 0x0250, 0x0350, 0x0450, 0x0550, 0x0650, 0x0750,
	0x0850, 0x0950, 0x0A50, 0x0B50, 0x0C50, 0x0D50, 0x0E50, 0x0F50,
	0x1050, 0x1150, 0x1250, 0x1350, 0x1450, 0x1550, 0x1650, 0x1750,
	0x1850, 0x1950, 0x1A50, 0x1B50, 0x1C50, 0x1D50, 0x1E50, 0x1F50,
	0x2050, 0x2150, 0x2250, 0x2350, 0x2450, 0x2550, 0x2650, 0x2750,
	0x2850, 0x2950, 0x2A50, 0x2B50, 0x2C50, 0x2D50, 0x2E50, 0x2F50,
	0x3050, 0x3150, 0x3250, 0x3350, 0x3450, 0x3550, 0x3650, 0x3750,
	0x3850, 0x3950, 0x3A50, 0x3B50, 0x3C50, 0x3D50, 0x3E50, 0x3F50,
	0x4050, 0x4150, 0x4250, 0x4350, 0x4450, 0x4550, 0x4650, 0x4750,
	0x4850, 0x4950, 0x4A50, 0x4B50, 0x4C50, 0x4D50, 0x4E50, 0x4F50,
	0x5050, 0x5150, 0x5250, 0x5350, 0x5450, 0x5550, 0x5650, 0x5750,
	0x5850, 0x5950, 0x5A50, 0x5B50, 0x5C50, 0x5D50, 0x5E50, 0x5F50,
	0x6050, 0x6150, 0x6250, 0x6350, 0x6450, 0x6550, 0x6650, 0x6750,
	0x6850, 0x6950, 0x6A50, 0x6B50, 0x6C50, 0x6D50, 0x6E50, 0x6F50,
	0x7050, 0x7150, 0x7250, 0x7350, 0x7450, 0x7550, 0x7650, 0x7750,
	0x7850, 0x7950, 0x7A50, 0x7B50, 0x7C50, 0x7D50, 0x7E50, 0x7F50,
	0x8050, 0x8150, 0x8250, 0x8350, 0x8450, 0x8550, 0x8650, 0x8750,
	0x8850, 0x8950, 0x8A50, 0x8B50, 0x8C50, 0x8D50, 0x8E50, 0x8F50,
	0x9050, 0x9150, 0x9250, 0x9350, 0x9450, 0x9550, 0x9650, 0x9750,
	0x9850, 0x9950, 0x9A50, 0x9B50, 0x9C50, 0x9D50, 0x9E50, 0x9F50,
	0xFA40, 0xFB40, 0xFC40, 0xFD40, 0xFE40, 0xFF40, 0x00C0, 0x0140,
	0x0240, 0x0340, 0x0440, 0x0540, 0x0640, 0x0740, 0x0840, 0x0940,
	0x0A40, 0x0B40, 0x0C40, 0x0D40, 0x0E40, 0x0F40, 0x1040, 0x1140,
	0x1240, 0x1340, 0x1440, 0x1540, 0x1640, 0x1740, 0x1840, 0x1940,
	0x1A40, 0x1B40, 0x1C40, 0x1D40, 0x1E40, 0x1F40, 0x2040, 0x2140,
	0x2240, 0x2340, 0x2440, 0x2540, 0x2640, 0x2740, 0x2840, 0x2940,
	0x2A40, 0x2B40, 0x2C40, 0x2D40, 0x2E40, 0x2F40, 0x3040, 0x3140,
	0x3240, 0x3340, 0x3440, 0x3540, 0x3640, 0x3740, 0x3840, 0x3940,
	0x3A40, 0x3B40, 0x3C40, 0x3D40, 0x3E40, 0x3F40, 0x4040, 0x4140,
	0x4240, 0x4340, 0x4440, 0x4540, 0x4640, 0x4740, 0x4840, 0x4940,
	0x4A40, 0x4B40, 0x4C40, 0x4D40, 0x4E40, 0x4F40, 0x5040, 0x5140,
	0x5240, 0x5340, 0x5440, 0x5540, 0x5640, 0x5740, 0x5840, 0x5940,
	0x5A40, 0x5B40, 0x5C40, 0x5D40, 0x5E40, 0x5F40, 0x6040, 0x6140,
	0x6240, 0x6340, 0x6440, 0x6540, 0x6640, 0x6740, 0x6840, 0x6940,
	0x6A40, 0x6B40, 0x6C40, 0x6D40, 0x6E40, 0x6F40, 0x7040, 0x7140,
	0x7240, 0x7340, 0x7440, 0x7540, 0x7640, 0x7740, 0x7840, 0x7940,
	0x7A40, 0x7B40, 0x7C40, 0x7D40, 0x7E40, 0x7F40, 0x8040, 0x8140,
	0x8240, 0x8340, 0x8440, 0x8540, 0x8640, 0x8740, 0x8840, 0x8940,
	0x8A40, 0x8B40, 0x8C40, 0x8D40, 0x8E40, 0x8F40, 0x9040, 0x9140,
	0x9240, 0x9340, 0x9440, 0x9540, 0x9640, 0x9740, 0x9840, 0x9940,
	0x9A40, 0x9B40, 0x9C40, 0x9D40, 0x9E40, 0x9F40, 0xA040, 0xA140,
	0xA240, 0xA340, 0xA440, 0xA540, 0xA640, 0xA740, 0xA840, 0xA940,
	0xAA40, 0xAB40, 0xAC40, 0xAD40, 0xAE40, 0xAF40, 0xB040, 0xB140,
	0xB240, 0xB340, 0xB440, 0xB540, 0xB640, 0xB740, 0xB840, 0xB940,
	0xBA40, 0xBB40, 0xBC40, 0xBD40, 0xBE40, 0xBF40, 0xC040, 0xC140,
	0xC240, 0xC340, 0xC440, 0xC540, 0xC640, 0xC740, 0xC840, 0xC940,
	0xCA40, 0xCB40, 0xCC40, 0xCD40, 0xCE40, 0xCF40, 0xD040, 0xD140,
	0xD240, 0xD340, 0xD440, 0xD540, 0xD640, 0xD740, 0xD840, 0xD940,
	0xDA40, 0xDB40, 0xDC40, 0xDD40, 0xDE40, 0xDF40, 0xE040, 0xE140,
	0xE240, 0xE340, 0xE440, 0xE540, 0xE640, 0xE740, 0xE840, 0xE940,
	0xEA40, 0xEB40, 0xEC40, 0xED40, 0xEE40, 0xEF40, 0xF040, 0xF140,
	0xF240, 0xF340, 0xF440, 0xF540, 0xF640, 0xF740, 0xF840, 0xF940,
	0x9A50, 0x9B50, 0x9C50, 0x9D50, 0x9E50, 0x9F50, 0xA050, 0xA150,
	0xA250, 0xA350, 0xA450, 0xA550, 0xA650, 0xA750, 0xA850, 0xA950,
	0xAA50, 0xAB50, 0xAC50, 0xAD50, 0xAE50, 0xAF50, 0xB050, 0xB150,
	0xB250, 0xB350, 0xB450, 0xB550, 0xB650, 0xB750, 0xB850, 0xB950,
	0xBA50, 0xBB50, 0xBC50, 0xBD50, 0xBE50, 0xBF50, 0xC050, 0xC150,
	0xC250, 0xC350, 0xC450, 0xC550, 0xC650, 0xC750, 0xC850, 0xC950,
	0xCA50, 0xCB50, 0xCC50, 0xCD50, 0xCE50, 0xCF50, 0xD050, 0xD150,
	0xD250, 0xD350, 0xD450, 0xD550, 0xD650, 0xD750, 0xD850, 0xD950,
	0xDA50, 0xDB50, 0xDC50, 0xDD50, 0xDE50, 0xDF50, 0xE050, 0xE150,
	0xE250, 0xE350, 0xE450, 0xE550, 0xE650, 0xE750, 0xE850, 0xE950,
	0xEA50, 0xEB50, 0xEC50, 0xED50, 0xEE50, 0xEF50, 0xF050, 0xF150,
	0xF250, 0xF350, 0xF450, 0xF550, 0xF650, 0xF750, 0xF850, 0xF950,
	0xFA50, 0xFB50, 0xFC50, 0xFD50, 0xFE50, 0xFF50, 0x00D0, 0x0150,
	0x0250, 0x0350, 0x0450, 0x0550, 0x0650, 0x0750, 0x0850, 0x0950,
	0x0A50, 0x0B50, 0x0C50, 0x0D50, 0x0E50, 0x0F50, 0x1050, 0x1150,
	0x1250, 0x1350, 0x1450, 0x1550, 0x1650, 0x1750, 0x1850, 0x1950,
	0x1A50, 0x1B50, 0x1C50, 0x1D50, 0x1E50, 0x1F50, 0x2050, 0x2150,
	0x2250, 0x2350, 0x2450, 0x2550, 0x2650, 0x2750, 0x2850, 0x2950,
	0x2A50, 0x2B50, 0x2C50, 0x2D50, 0x2E50, 0x2F50, 0x3050, 0x3150,
	0x3250, 0x3350, 0x3450, 0x3550, 0x3650, 0x3750, 0x3850, 0x3950,
	0x3A50, 0x3B50, 0x3C50, 0x3D50, 0x3E50, 0x3F50, 0x4050, 0x4150,
	0x4250, 0x4350, 0x4450, 0x4550, 0x4650, 0x4750, 0x4850, 0x4950,
	0x4A50, 0x4B50, 0x4C50, 0x4D50, 0x4E50, 0x4F50, 0x5050, 0x5150,
	0x5250, 0x5350, 0x5450, 0x5550, 0x5650, 0x5750, 0x5850, 0x5950,
	0x5A50, 0x5B50, 0x5C50, 0x5D50, 0x5E50, 0x5F50, 0x6050, 0x6150,
	0x6250, 0x6350, 0x6450, 0x6550, 0x6650, 0x6750, 0x6850, 0x6950,
	0x6A50, 0x6B50, 0x6C50, 0x6D50, 0x6E50, 0x6F50, 0x7050, 0x7150,
	0x7250, 0x7350, 0x7450, 0x7550, 0x7650, 0x7750, 0x7850, 0x7950,
	0x7A50, 0x7B50, 0x7C50, 0x7D50, 0x7E50, 0x7F50, 0x8050, 0x8150,
	0x8250, 0x8350, 0x8450, 0x8550, 0x8650, 0x8750, 0x8850, 0x8950,
	0x8A50, 0x8B50, 0x8C50, 0x8D50, 0x8E50, 0x8F50, 0x9050, 0x9150,
	0x9250, 0x9350, 0x9450, 0x9550, 0x9650, 0x9750, 0x9850, 0x9950,
};

u8 ZeroTable[256] = {
	0x80, 0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
	0,	  0,	0,	  0,	0,	  0,	0,	  0, 0, 0, 0, 0, 0, 0, 0, 0,
};

#define GBSAVE_GAME_VERSION_1 1
#define GBSAVE_GAME_VERSION_2 2
#define GBSAVE_GAME_VERSION_3 3
#define GBSAVE_GAME_VERSION_4 4
#define GBSAVE_GAME_VERSION_5 5
#define GBSAVE_GAME_VERSION_6 6
#define GBSAVE_GAME_VERSION_7 7
#define GBSAVE_GAME_VERSION_8 8
#define GBSAVE_GAME_VERSION_9 9
#define GBSAVE_GAME_VERSION_10 10
#define GBSAVE_GAME_VERSION_11 11
#define GBSAVE_GAME_VERSION_12 12
#define GBSAVE_GAME_VERSION_13 13
#define GBSAVE_GAME_VERSION_14 14
#define GBSAVE_GAME_VERSION GBSAVE_GAME_VERSION_13

#if 1
u16 gbcGetNewBGR15(int r, int g, int b)
{
	r = (r * 13 + g * 2 + b * 1 + 8) >> 4;
	g = (r * 1 + g * 12 + b * 3 + 8) >> 4;
	b = (r * 2 + g * 2 + b * 12 + 8) >> 4;
	return (b << 10) | (g << 5) | r;
}

void gbGenFilter()
{
	for (int r = 0; r < 32; r++)
	{
		for (int g = 0; g < 32; g++)
		{
			for (int b = 0; b < 32; b++)
			{
				gbColorFilter[(b << 10) | (g << 5) | r] = gbcGetNewBGR15(r, g, b);
			}
		}
	}
}
#else
int inline gbGetValue(int min, int max, int v)
{
	return (int)(min + (float)(max - min) * (2.0 * (v / 31.0) - (v / 31.0) * (v / 31.0)));
}

void gbGenFilter()
{
	for (int r = 0; r < 32; r++)
	{
		for (int g = 0; g < 32; g++)
		{
			for (int b = 0; b < 32; b++)
			{
				int nr = gbGetValue(gbGetValue(4, 14, g),
				                    gbGetValue(24, 29, g), r) - 4;
				int ng = gbGetValue(gbGetValue(4 + gbGetValue(0, 5, r),
				                               14 + gbGetValue(0, 3, r), b),
				                    gbGetValue(24 + gbGetValue(0, 3, r),
				                               29 + gbGetValue(0, 1, r), b), g) - 4;
				int nb = gbGetValue(gbGetValue(4 + gbGetValue(0, 5, r),
				                               14 + gbGetValue(0, 3, r), g),
				                    gbGetValue(24 + gbGetValue(0, 3, r),
				                               29 + gbGetValue(0, 1, r), g), b) - 4;
				gbColorFilter[(b << 10) | (g << 5) | r] = (nb << 10) | (ng << 5) | nr;
			}
		}
	}
}
#endif

// used for OAM DMA only
static void gbOamDmaCopyMemory(u16 address)
{
	if (address < 0xe000)
	{
		//memcpy(&gbMemoryMap[0x0f][0x0e00], &gbMemoryMap[s >> 12][s & 0x0f00], 0xa0);

		// FIXME: Is it correct to apply cheats this way?
		u16 dst = 0x0e00;
		u16 src = address;
		for (int count = 0xa0; count > 0; --count)
		{
			gbMemoryMap[0x0f][dst] = gbCheatMap[src] ? gbCheatRead(src) : gbMemoryMap[src >> 12][src & 0x0fff];
			++dst;
			++src;
		}
	}
	else
	{
		memset(&gbMemoryMap[0x0f][0x0e00], 0xff, 0xa0);
	}


}

// HDMA
static void gbHdmaCopyMemory(u16 dst, u16 src, int count)
{
	while (count--)
	{
#if 0
		gbMemoryMap[dst >> 12][dst & 0x0fff] = (((src & 0xe000) == 0x8000) || src >= 0xfe00) ? 
			0xff : gbMemoryMap[src >> 12][src & 0x0fff];
#else
		gbMemoryMap[dst >> 12][dst & 0x0fff] = (((src & 0xe000) == 0x8000) || src >= 0xfe00) ? 
			0xff : (gbCheatMap[src] ? gbCheatRead(src) : gbMemoryMap[src >> 12][src & 0x0fff]);
#endif
		++dst;
		++src;
	}
}

void gbDoHdma()
{
	gbHdmaBytes = ((register_HDMA5 & 0x7f) + 1) * 0x10;
	int length = gbHdmaOn ? 0x10 : gbHdmaBytes;

	if ((gbHdmaDestination + length) & 0x10000)
	{
		length = 0x10000 - gbHdmaDestination;
		gbMemory[0xff55] = register_HDMA5 |= 0x80;
	}

	gbHdmaCopyMemory((gbHdmaDestination & 0x1fff) | 0x8000, gbHdmaSource, length);

	gbHdmaDestination += length;
	gbHdmaSource	  += length;
	gbHdmaBytes		  -= length;

	// fixes The Adventures of the Smurfs
	if (!(register_LCDC & 0x80))
		gbHdmaBytes = 0;

#if 1
	gbMemory[0xff51] = register_HDMA1 = (gbHdmaSource >> 8) & 0xff;
	gbMemory[0xff52] = register_HDMA2 = gbHdmaSource & 0xf0;
	gbMemory[0xff53] = register_HDMA3 = (gbHdmaDestination >> 8) & 0x1f;
	gbMemory[0xff54] = register_HDMA4 = gbHdmaDestination & 0xf0;
#else
	gbMemory[0xff51] = register_HDMA1 = 0xff;
	gbMemory[0xff52] = register_HDMA2 = 0xff;
	gbMemory[0xff53] = register_HDMA3 = 0xff;
	gbMemory[0xff54] = register_HDMA4 = 0xff;
#endif

	gbMemory[0xff55] = register_HDMA5 = (gbHdmaBytes / 0x10 - 1) | (register_HDMA5 & 0x80);
	if (register_HDMA5 & 0x80)
		gbHdmaOn = 0;

	// we need to take the time it takes to complete the transfer into
	// account... according to GB DEV FAQs, the setup time is the same
	// for single and double speed, but the actual transfer takes the
	// same time
	if (gbSpeed)
		gbDmaTicks = 2 + length;
	else
		gbDmaTicks = 1 + length / 2;

	// ?
	if (IFF & 0x80)
		gbDmaTicks++;
}

// fix for Harley and Lego Racers
void gbCompareLYToLYC()
{
	if (register_LCDC & 0x80)
	{
		if (register_LY == register_LYC)
		{
			// mark that we have a match
			register_STAT |= 4;

			// check if we need an interrupt
			if (register_STAT & 0x40)
			{
				// send LCD interrupt only if no interrupt 48h signal...
				if (!gbInt48Signal)
				{
					register_IF |= 2;
				}
				gbInt48Signal |= 8;
			}
		}
		else // no match
		{
			register_STAT &= 0xfb;
			gbInt48Signal &= ~8;
		}
	}
}

// less hack-ish fix for 007 and The Smurfs
void gbCompareLYToWY()
{
//	if (gbWindowLine < 144)
		inUseRegister_WY = register_WY;

	if ((register_LCDC & 0x01 || gbCgbMode) && (register_LCDC & 0x20))
		if (gbWindowLine < 0)
		{
			if (register_LY > inUseRegister_WY)
				gbWindowLine = 146;
			else
				gbWindowLine = 0;
		}
}

void gbWriteMemoryWrapped(register u16 address, register u8 value)
{
	if (address < 0x8000)
	{
#ifndef FINAL_VERSION
		if (memorydebug && (address > 0x3fff || address < 0x2000))
		{
			log("Memory register write %04x=%02x PC=%04x\n",
			    address,
			    value,
			    PC.W);
		}
#endif
		if (mapper)
			(*mapper)(address, value);
		return;
	}

	if (address < 0xa000)
	{
		// No access to Vram during mode 3
		// (used to emulate the gfx differences between GB & GBC-GBA/SP in Stunt Racer)
		if ((gbLcdModeDelayed != 3) ||
		    // This part is used to emulate a small difference between hardwares
		    // (check 8-in-1's arrow on GBA/GBC to verify it)
		    ((register_LY == 0) && ((gbHardware & 0xa) && (gbScreenOn == false) &&
		                            (register_LCDC & 0x80)) &&
		     (gbLcdLYIncrementTicksDelayed == (GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS))))
			gbMemoryMap[address >> 12][address & 0x0fff] = value;
		return;
	}

	// Used for the mirroring of 0xC000 in 0xE000
	if ((address >= 0xe000) && (address < 0xfe00))
		address &= ~0x2000;

	if (address < 0xc000)
	{
#ifndef FINAL_VERSION
		if (memorydebug)
		{
			log("Memory register write %04x=%02x PC=%04x\n",
			    address,
			    value,
			    PC.W);
		}
#endif

		// I believe this is a correct fix (it used to be 'if (mapper)')...
		if (mapperRAM)
			(*mapperRAM)(address, value);
		return;
	}

	if (address < 0xfe00)
	{
		gbMemoryMap[address >> 12][address & 0x0fff] = value;
		return;
	}

	// OAM not accessible during mode 2 & 3.
	if (address < 0xfea0)
	{
		if (((gbHardware & 0xa) && ((gbLcdMode | gbLcdModeDelayed) & 2)) ||
		    ((gbHardware & 5) && (((gbLcdModeDelayed == 2) &&
		                           (gbLcdTicksDelayed <= GBLCD_MODE_2_CLOCK_TICKS)) ||
		                          (gbLcdModeDelayed == 3))))
			return;
		else
		{
			gbMemory[address] = value;
			return;
		}
	}

	if ((address > 0xfea0) && (address < 0xff00)) // GBC allows reading/writing to that area
	{
		gbMemory[address] = value;
		return;
	}

	switch (address & 0x00ff)
	{
	case 0x00:
	{
		gbMemory[0xff00] = ((gbMemory[0xff00] & 0xcf) |
		                    (value & 0x30) | 0xc0);
		if (gbSgbMode)
		{
			gbSgbDoBitTransfer(value);
		}
		return;
	}

	case 0x01:
	{
		gbMemory[0xff01] = value;
		return;
	}

	// serial control
	case 0x02:
	{
		gbSerialOn		 = (value & 0x80);
		gbMemory[0xff02] = value;
		if (gbSerialOn)
		{
			gbSerialTicks = GBSERIAL_CLOCK_TICKS;
#ifdef OLD_GB_LINK
			if (linkConnected)
			{
				if (value & 1)
				{
					linkSendByte(0x100 | gbMemory[0xFF01]);
					Sleep(5);
				}
			}
#endif
		}

		gbSerialBits = 0;
		return;
	}

	case 0x04:
	{
		// DIV register resets on any write
		// (not totally perfect, but better than nothing)
		gbMemory[0xff04] = register_DIV = 0;
		gbDivTicks		 = GBDIV_CLOCK_TICKS;
		// Another weird timer 'bug' :
		// Writing to DIV register resets the internal timer,
		// and can also increase TIMA/trigger an interrupt
		// in some cases...
		if (gbTimerOn && !(gbInternalTimer & (gbTimerClockTicks >> 1)))
		{
			gbMemory[0xff05] = ++register_TIMA;
			if (register_TIMA == 0)
			{
				// timer overflow!

				// reload timer modulo
				gbMemory[0xff05] = register_TIMA = register_TMA;

				// flag interrupt
				gbMemory[0xff0f] = register_IF |= 4;
			}
		}
		gbInternalTimer = 0xff;
		return;
	}
	case 0x05:
		gbMemory[0xff05] = register_TIMA = value;
		return;

	case 0x06:
		gbMemory[0xff06] = register_TMA = value;
		return;

	// TIMER control
	case 0x07:
	{
		gbTimerModeChange = (((value & 3) != (register_TAC & 3)) && (value & register_TAC & 4)) ? true : false;
		gbTimerOnChange	  = (((value ^ register_TAC) & 4) == 4) ? true : false;

		gbTimerOn = (value & 4) ? true : false;

		if (gbTimerOnChange || gbTimerModeChange)
		{
			gbTimerMode = value & 3;

			switch (gbTimerMode)
			{
			case 0:
				gbTimerClockTicks = GBTIMER_MODE_0_CLOCK_TICKS;
				break;
			case 1:
				gbTimerClockTicks = GBTIMER_MODE_1_CLOCK_TICKS;
				break;
			case 2:
				gbTimerClockTicks = GBTIMER_MODE_2_CLOCK_TICKS;
				break;
			case 3:
				gbTimerClockTicks = GBTIMER_MODE_3_CLOCK_TICKS;
				break;
			}
		}

		// This looks weird, but this emulates a bug in which register_TIMA
		// is increased when writing to register_TAC
		// (This fixes Korodice's long-delay between menus bug).

		if (gbTimerOnChange || gbTimerModeChange)
		{
			bool temp = false;

			if ((gbTimerOn  && !gbTimerModeChange)  && (gbTimerMode & 2) &&
			    !(gbInternalTimer & 0x80) && (gbInternalTimer & (gbTimerClockTicks >> 1)) &&
			    !(gbInternalTimer & (gbTimerClockTicks >> 5)))
				temp = true;
			else if ((!gbTimerOn && !gbTimerModeChange && gbTimerOnChange) &&  ((gbTimerTicks - 1) < (gbTimerClockTicks >> 1)))
				temp = true;
			else if (gbTimerOn && gbTimerModeChange && !gbTimerOnChange)
			{
				switch (gbTimerMode & 3)
				{
				case 0x00:
					temp = false;
					break;
				case 0x01:
					if (((gbInternalTimer & 0x82) == 2) && (gbTimerTicks > (gbClockTicks + 1)))
						temp = true;
					break;
				case 0x02:
					if (((gbInternalTimer & 0x88) == 0x8) && (gbTimerTicks > (gbClockTicks + 1)))
						temp = true;
					break;
				case 0x03:
					if (((gbInternalTimer & 0xA0) == 0x20) && (gbTimerTicks > (gbClockTicks + 1)))
						temp = true;
					break;
				}
			}

			if (temp)
			{
				gbMemory[0xff05] = ++register_TIMA;
				if ((register_TIMA & 0xff) == 0)
				{
					// timer overflow!

					// reload timer modulo
					gbMemory[0xff05] = register_TIMA = register_TMA;

					// flag interrupt
					gbMemory[0xff0f] = register_IF |= 4;
				}
			}
		}
		gbMemory[0xff07] = register_TAC = value;
		return;
	}

	case 0x0f:
	{
		gbMemory[0xff0f] = register_IF = value;
		//gbMemory[0xff0f] = register_IE |= value;
		return;
	}

	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13:
	case 0x14:
	case 0x16:
	case 0x17:
	case 0x18:
	case 0x19:
	case 0x1a:
	case 0x1b:
	case 0x1c:
	case 0x1d:
	case 0x1e:
	case 0x20:
	case 0x21:
	case 0x22:
	case 0x23:
	case 0x24:
	case 0x25:
	{
		if (gbMemory[NR52] & 0x80)
		{
			SOUND_EVENT(address, value);
			return;
		}
	}

	case 0x26:
	{
		SOUND_EVENT(address, value);
		return;
	}

	case 0x30:
	case 0x31:
	case 0x32:
	case 0x33:
	case 0x34:
	case 0x35:
	case 0x36:
	case 0x37:
	case 0x38:
	case 0x39:
	case 0x3a:
	case 0x3b:
	case 0x3c:
	case 0x3d:
	case 0x3e:
	case 0x3f:
	{
		SOUND_EVENT(address, value);
		//gbMemory[address] = value;
		return;
	}

	case 0x40:
	{
		int lcdChange = (register_LCDC & 0x80) ^ (value & 0x80);

		// don't draw the window if it was not enabled and not being drawn before
		if (!(register_LCDC & 0x20) && (value & 0x20))
		{
			if (gbWindowLine < 0 && register_LY > register_WY)
				gbWindowLine = 146;
			// 007 fix : don't draw the first window's 1st line if it's enable 'too late'
			// (ie. if register_LY == register_WY when enabling it)
			// and move it to the next line
			else if (gbLcdModeDelayed == 3)
				gbWindowLine = -2;
		}

		gbMemory[0xff40] = register_LCDC = value;

		if (lcdChange)
		{
			if ((value & 0x80) && (!register_LCDCBusy))
			{
				//  if (!gbWhiteScreen && !gbSgbMask)

				//  systemDrawScreen();

				gbRegisterLYLCDCOffOn = (register_LY + 144) % 154;

				gbLcdTicks		  = GBLCD_MODE_2_CLOCK_TICKS - (gbSpeed ? 2 : 1);
				gbLcdTicksDelayed = GBLCD_MODE_2_CLOCK_TICKS - (gbSpeed ? 1 : 0);
				gbLcdLYIncrementTicks		 = GBLY_INCREMENT_CLOCK_TICKS - (gbSpeed ? 2 : 1);
				gbLcdLYIncrementTicksDelayed = GBLY_INCREMENT_CLOCK_TICKS - (gbSpeed ? 1 : 0);
				gbLcdMode = 2;
				gbLcdModeDelayed	= 2;
				gbMemory[0xff41]	= register_STAT = (register_STAT & 0xfc) | 2;
				gbMemory[0xff44]	= register_LY = 0x00;
				gbInt48Signal		= 0;
				gbLYChangeHappened	= false;
				gbLCDChangeHappened = false;
				gbWindowLine		= 146;
				inUseRegister_WY	= 146;
				inUseRegister_WX	= 192;

				// Fix for Namco Gallery Vol.2
				// (along with updating register_LCDC at the start of 'case 0x40') :
				if (register_STAT & 0x20)
				{
					// send LCD interrupt only if no interrupt 48h signal...
					if (!gbInt48Signal)
					{
						gbMemory[0xff0f] = register_IF |= 2;
					}
					gbInt48Signal |= 4;
				}
				gbCompareLYToLYC();
			}
			else
			{
				register_LCDCBusy = gbClockTicks + 6;

				//used to update the screen with white lines when it's off.
				//(it looks strange, but it's pretty accurate)

				gbWhiteScreen = 0;

				gbScreenTicks = ((150 - register_LY) * GBLY_INCREMENT_CLOCK_TICKS +
				                 (49 << (gbSpeed ? 1 : 0)));

				// disable the screen rendering
				gbScreenOn		 = false;
				gbLcdTicks		 = 0;
				gbLcdMode		 = 0;
				gbLcdModeDelayed = 0;
				gbMemory[0xff41] = register_STAT &= 0xfc;
				gbInt48Signal	 = 0;
			}
		}
		return;
	}

	// STAT
	case 0x41:
	{
		//register_STAT = (register_STAT & 0x87) |
		//      (value & 0x7c);
		gbMemory[0xff41] = register_STAT = (value & 0xf8) | (register_STAT & 0x07); // fix ?
		// GB bug from Devrs FAQ
		// proper fix
		gbInt48Signal &= ((register_STAT >> 3) & 0xF);

		if ((register_LCDC & 0x80))
		{
			if ((register_STAT & 0x08) && (gbLcdMode == 0))
			{
				if (!gbInt48Signal)
				{
					gbMemory[0xff0f] = register_IF |= 2;
				}
				gbInt48Signal |= 1;
			}
			if ((register_STAT & 0x10) && (gbLcdMode == 1))
			{
				if (!gbInt48Signal)
				{
					gbMemory[0xff0f] = register_IF |= 2;
				}
				gbInt48Signal |= 2;
			}
			if ((register_STAT & 0x20) && (gbLcdMode == 2))
			{
				if (!gbInt48Signal)
				{
					gbMemory[0xff0f] = register_IF |= 2;
				}
				gbInt48Signal |= 4;
			}
			gbCompareLYToLYC();

			gbMemory[0xff0f] = register_IF;
			gbMemory[0xff41] = register_STAT;
		}
		return;
	}

	// SCY
	case 0x42:
	{
		int temp = -1;

		if ((gbLcdMode == 3) || (gbLcdModeDelayed == 3))
			temp = ((GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS) -
			        gbLcdLYIncrementTicks);

		if (temp >= 0)
		{
			for (int i = temp << (gbSpeed ? 1 : 2); i < 300; i++)
				if (temp < 300)
					gbSCYLine[i] = value;
		}

		else
			memset(gbSCYLine, value, sizeof(gbSCYLine));

		gbMemory[0xff42] = register_SCY = value;
		return;
	}

	// SCX
	case 0x43:
	{
		int temp = -1;

		if (gbLcdModeDelayed == 3)
			temp = ((GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS) -
			        gbLcdLYIncrementTicksDelayed);

		if (temp >= 0)
		{
			for (int i = temp << (gbSpeed ? 1 : 2); i < 300; i++)
				if (temp < 300)
					gbSCXLine[i] = value;
		}

		else
			memset(gbSCXLine, value, sizeof(gbSCXLine));

		gbMemory[0xff43] = register_SCX = value;
		return;
	}

	// LY
	case 0x44:
	{
		// read only
		return;
	}

	// LYC
	case 0x45:
	{
		if (register_LYC != value)
		{
			gbMemory[0xff45] = register_LYC = value;
			if (register_LCDC & 0x80)
			{
				gbCompareLYToLYC();
			}
		}
		return;
	}

	// DMA!
	case 0x46:
	{
		// TODO: forbide access to certain memory areas until OAM DMA is over
		int source = value * 0x0100;

		gbOamDmaCopyMemory(source);
		gbMemory[0xff46] = register_DMA = value;

		return;
	}

	// BGP
	case 0x47:
	{
		int temp = -1;

		gbMemory[0xff47] = value;

		if (gbLcdModeDelayed == 3)
			temp = ((GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS) -
			        gbLcdLYIncrementTicksDelayed);

		if (temp >= 0)
		{
			for (int i = temp << (gbSpeed ? 1 : 2); i < 300; i++)
				if (temp < 300)
					gbBgpLine[i] = value;
		}
		else
			memset(gbBgpLine, value, sizeof(gbBgpLine));

		gbBgp[0] = value & 0x03;
		gbBgp[1] = (value & 0x0c) >> 2;
		gbBgp[2] = (value & 0x30) >> 4;
		gbBgp[3] = (value & 0xc0) >> 6;
		break;
	}

	// OBP0
	case 0x48:
	{
		int temp = -1;

		gbMemory[0xff48] = value;

		if (gbLcdModeDelayed == 3)
			temp = ((GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS) -
			        gbLcdLYIncrementTicksDelayed);

		if (temp >= 0)
		{
			for (int i = temp << (gbSpeed ? 1 : 2); i < 300; i++)
				if (temp < 300)
					gbObp0Line[i] = value;
		}
		else
			memset(gbObp0Line, value, sizeof(gbObp0Line));

		gbObp0[0] = value & 0x03;
		gbObp0[1] = (value & 0x0c) >> 2;
		gbObp0[2] = (value & 0x30) >> 4;
		gbObp0[3] = (value & 0xc0) >> 6;
		break;
	}

	// OBP1
	case 0x49:
	{
		int temp = -1;

		gbMemory[0xff49] = value;

		if (gbLcdModeDelayed == 3)
			temp = ((GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS) -
			        gbLcdLYIncrementTicksDelayed);

		if (temp >= 0)
		{
			for (int i = temp << (gbSpeed ? 1 : 2); i < 300; i++)
				if (temp < 300)
					gbObp1Line[i] = value;
		}
		else
			memset(gbObp1Line, value, sizeof(gbObp1Line));

		gbObp1[0] = value & 0x03;
		gbObp1[1] = (value & 0x0c) >> 2;
		gbObp1[2] = (value & 0x30) >> 4;
		gbObp1[3] = (value & 0xc0) >> 6;
		break;
	}

	// WY
	case 0x4a:
	{
		gbMemory[0xff4a] = register_WY = value;
		if ((register_LY <= register_WY) && ((gbWindowLine < 0) || (gbWindowLine >= 144)))
		{
			gbWindowLine = -1;
		}
		if (gbLcdModeDelayed != 3)
		{
			gbCompareLYToWY();
		}
		return;
	}

	// WX
	case 0x4b:
	{
		gbMemory[0xff4b] = register_WX = value;
		if (gbLcdModeDelayed != 3)
		{
			// this does fix the missing line in Zen - Interglactic Ninja
			inUseRegister_WX = register_WX;
		}
		return;
	}

	// KEY1
	case 0x4d:
	{
		if (gbCgbMode)
		{
			gbMemory[0xff4d] = (gbMemory[0xff4d] & 0x80) | (value & 1) | 0x7e;
			return;
		}
		break;
	}

	// VBK
	case 0x4f:
	{
		if (gbCgbMode)
		{
			value = value & 1;
			if (value == gbVramBank)
				return;

			int vramAddress = value * 0x2000;
			gbMemoryMap[0x08] = &gbVram[vramAddress];
			gbMemoryMap[0x09] = &gbVram[vramAddress + 0x1000];

			gbVramBank	 = value;
			register_VBK = value;
		}
		return;
	}

	// BOOTROM disable register (also gbCgbMode enabler if value & 0x10 ?)
	case 0x50:
	{
		if (useBios && inBios && !skipBios && (value & 1))
		{
			gbMemoryMap[0x00] = &gbRom[0x0000];
			memcpy((u8 *)(gbRom + 0x100), (u8 *)(gbMemory + 0x100), 0xF00);
			inBios = false;
		}
	}

	// HDMA1
	case 0x51:
	{
		if (gbCgbMode)
		{
			gbHdmaSource = (value << 8) | (gbHdmaSource & 0xf0);
			register_HDMA1 = value;
			return;
		}
		break;
	}

	// HDMA2
	case 0x52:
	{
		if (gbCgbMode)
		{
			value &= 0xf0;
			gbHdmaSource = (gbHdmaSource & 0xff00) | (value);
			register_HDMA2 = value;
			return;
		}
		break;
	}

	// HDMA3
	case 0x53:
	{
		if (gbCgbMode)
		{
			gbHdmaDestination  = (value << 8) | (gbHdmaDestination & 0xf0);
			register_HDMA3	   = value;
			return;
		}
		break;
	}

	// HDMA4
	case 0x54:
	{
		if (gbCgbMode)
		{
			value &= 0xf0;
			gbHdmaDestination  = (gbHdmaDestination & 0xff00) | value;
			register_HDMA4	   = value;
			return;
		}
		break;
	}

	// HDMA5
	case 0x55:
	{
		if (gbCgbMode)
		{
			gbMemory[0xff55] = register_HDMA5 = (value & 0x7f);

			if (gbHdmaOn)
			{
				if (!(value & 0x80))
				{
					gbMemory[0xff55] = register_HDMA5 |= 0x80;
					if (gbLcdModeDelayed == 0)
					{
						gbHdmaOn = 0;
					}
				}
			}
			else
			{
				if (value & 0x80)
				{
					gbHdmaOn = 1;
					if (gbLcdModeDelayed == 0)
						gbDoHdma();
				}
				else
				{
					gbDoHdma();
				}
			}
			return;
		}
		break;
	}

	// BCPS
	case 0x68:
	{
		if (gbCgbMode)
		{
			int paletteIndex = (value & 0x3f) >> 1;
			int paletteHiLo	 = (value & 0x01);

			gbMemory[0xff68] = value;
			gbMemory[0xff69] = (paletteHiLo ?
			                    (gbPalette[paletteIndex] >> 8) :
			                    (gbPalette[paletteIndex] & 0x00ff));
			return;
		}
		break;
	}

	// BCPD
	case 0x69:
	{
		if (gbCgbMode)
		{
			int v = gbMemory[0xff68];
			int paletteIndex = (v & 0x3f) >> 1;
			int paletteHiLo	 = (v & 0x01);

			// No access to gbPalette during mode 3 (Color Panel Demo)
			if (((gbLcdModeDelayed != 3) && (!((gbLcdMode == 0) && (gbLcdTicks >= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 1)))) && (!gbSpeed)) ||
			    (gbSpeed && ((gbLcdMode == 1) || (gbLcdMode == 2) ||
			                 ((gbLcdMode == 3) && (gbLcdTicks > (GBLCD_MODE_3_CLOCK_TICKS - 2))) ||
			                 ((gbLcdMode == 0) && (gbLcdTicks <= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 2))))))
			{
				gbMemory[0xff69]		= value;
				gbPalette[paletteIndex] = (paletteHiLo ?
				                           ((value << 8) | (gbPalette[paletteIndex] & 0xff)) :
				                           ((gbPalette[paletteIndex] & 0xff00) | (value))) & 0x7fff;
			}

			if (gbMemory[0xff68] & 0x80)
			{
				int index = ((gbMemory[0xff68] & 0x3f) + 1) & 0x3f;

				gbMemory[0xff68] = (gbMemory[0xff68] & 0x80) | index;
				gbMemory[0xff69] = (index & 1 ?
				                    (gbPalette[index >> 1] >> 8) :
				                    (gbPalette[index >> 1] & 0x00ff));
			}
			return;
		}
		break;
	}

	// OCPS
	case 0x6a:
	{
		if (gbCgbMode)
		{
			int paletteIndex = (value & 0x3f) >> 1;
			int paletteHiLo	 = (value & 0x01);

			paletteIndex += 32;

			gbMemory[0xff6a] = value;
			gbMemory[0xff6b] = (paletteHiLo ?
			                    (gbPalette[paletteIndex] >> 8) :
			                    (gbPalette[paletteIndex] & 0x00ff));
			return;
		}
		break;
	}

	// OCPD
	case 0x6b:
	{
		if (gbCgbMode)
		{
			int v = gbMemory[0xff6a];
			int paletteIndex = (v & 0x3f) >> 1;
			int paletteHiLo	 = (v & 0x01);

			paletteIndex += 32;

			// No access to gbPalette during mode 3 (Color Panel Demo)
			if (((gbLcdModeDelayed != 3) && (!((gbLcdMode == 0) && (gbLcdTicks >= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 1)))) && (!gbSpeed)) ||
			    (gbSpeed && ((gbLcdMode == 1) || (gbLcdMode == 2) ||
			                 ((gbLcdMode == 3) && (gbLcdTicks > (GBLCD_MODE_3_CLOCK_TICKS - 2))) ||
			                 ((gbLcdMode == 0) && (gbLcdTicks <= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 2))))))
			{
				gbMemory[0xff6b]		= value;
				gbPalette[paletteIndex] = (paletteHiLo ?
				                           ((value << 8) | (gbPalette[paletteIndex] & 0xff)) :
				                           ((gbPalette[paletteIndex] & 0xff00) | (value))) & 0x7fff;
			}

			if (gbMemory[0xff6a] & 0x80)
			{
				int index = ((gbMemory[0xff6a] & 0x3f) + 1) & 0x3f;

				gbMemory[0xff6a] = (gbMemory[0xff6a] & 0x80) | index;

				gbMemory[0xff6b] = (index & 1 ?
				                    (gbPalette[(index >> 1) + 32] >> 8) :
				                    (gbPalette[(index >> 1) + 32] & 0x00ff));
			}
			return;
		}
		break;
	}

	case 0x6c:
	{
		gbMemory[0xff6c] = 0xfe | value;
		return;
	}

	// SVBK
	case 0x70:
	{
		if (gbCgbMode)
		{
			value = value & 7;

			int bank = value;
			if (value == 0)
				bank = 1;

			if (bank == gbWramBank)
				return;

			int wramAddress = bank * 0x1000;
			gbMemoryMap[0x0d] = &gbWram[wramAddress];

			gbWramBank		 = bank;
			gbMemory[0xff70] = register_SVBK = value;
			return;
		}
		break;
	}

	case 0x75:
	{
		gbMemory[0xff75] = 0x8f | value;
		return;
	}

	case 0xff:
	{
		gbMemory[0xffff] = register_IE = value;
		return;
	}
	}

	if (address < 0xff80)
	{
		gbMemory[address] = value;
		return;
	}

	gbMemory[address] = value;
}

void gbWriteMemory(register u16 address, register u8 value)
{
	gbWriteMemoryWrapped(address, value);
	CallRegisteredLuaMemHook(address, 1, value, LUAMEMHOOK_WRITE);
}

u8 gbReadOpcode(register u16 address)
{
	if (gbCheatMap[address])
		return gbCheatRead(address);

	if (address < 0x8000)
		return gbMemoryMap[address >> 12][address & 0x0fff];

	if (address < 0xa000)
	{
		// A lot of 'ugly' checks... But only way to emulate this particular behaviour...
		if (
		    (
		        (gbHardware & 0xa) &&
		        (
		            (gbLcdModeDelayed != 3) ||
		            (
		                ((register_LY == 0) && (gbScreenOn == false) && (register_LCDC & 0x80)) &&
		                (gbLcdLYIncrementTicksDelayed == (GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS))
		            )
		        )
		    )
		    ||
		    (
		        (gbHardware & 0x5) &&
		        (gbLcdModeDelayed != 3) &&
		        (
		            (gbLcdMode != 3) ||
		            ((register_LY == 0) && ((gbScreenOn == false) && (register_LCDC & 0x80)) &&
		             (gbLcdLYIncrementTicks == (GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS)))
		        )
		    )
		    )
			return gbMemoryMap[address >> 12][address & 0x0fff];

		return 0xff;
	}

	// Used for the mirroring of 0xC000 in 0xE000
	if ((address >= 0xe000) && (address < 0xfe00))
		address &= ~0x2000;

	switch (address & 0xf000)
	{
	case 0x0a:
	case 0x0b:
		if (mapperReadRAM)
			return mapperReadRAM(address);
		break;
	case 0x0f:
		if (address > 0xff00)
		{
			switch (address & 0x00ff)
			{
			case 0x02:
				return (gbMemory[0xff02]);
			case 0x03:
				return (0xff);
			case 0x04:
				return register_DIV;
			case 0x05:
				return register_TIMA;
			case 0x06:
				return register_TMA;
			case 0x07:
				return (0xf8 | register_TAC);
			case 0x08:
			case 0x09:
			case 0x0a:
			case 0x0b:
			case 0x0c:
			case 0x0d:
			case 0x0e:
				return (0xff);
			case 0x0f:
				return (0xe0 | gbMemory[0xff0f]);
			case 0x40:
				return register_LCDC;
			case 0x41:
				// This is a GB/C only bug (ie. not GBA/SP).
				if ((gbHardware & 7) && (gbLcdMode == 2) && (gbLcdModeDelayed == 1) && (!gbSpeed))
					return (0x80 | (gbMemory[0xff41] & 0xFC));
				else
					return (0x80 | gbMemory[0xff41]);
			case 0x42:
				return register_SCY;
			case 0x43:
				return register_SCX;
			case 0x44:
				if (((gbHardware & 7) && ((gbLcdMode == 1) && (gbLcdTicks == 0x71))) ||
				    (!(register_LCDC & 0x80)))
					return 0;
				else
					return register_LY;
			case 0x45:
				return register_LYC;
			case 0x46:
				return register_DMA;
			case 0x4a:
				return register_WY;
			case 0x4b:
				return register_WX;
			case 0x4c:
				return 0xff;
			case 0x4f:
				return (0xfe | register_VBK);
			case 0x51:
				return register_HDMA1;
			case 0x52:
				return register_HDMA2;
			case 0x53:
				return register_HDMA3;
			case 0x54:
				return register_HDMA4;
			case 0x55:
				return register_HDMA5;
			case 0x68:
			case 0x6a:
				if (gbCgbMode)
					return (0x40 | gbMemory[address]);
				else
					return 0xc0;
			case 0x69:
			case 0x6b:
				if (gbCgbMode)
				{
					// No access to gbPalette during mode 3 (Color Panel Demo)
					if (((gbLcdModeDelayed != 3) &&
					     (!((gbLcdMode == 0) && (gbLcdTicks >= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 1)))) && (!gbSpeed)) ||
					    (gbSpeed && ((gbLcdMode == 1) || (gbLcdMode == 2) ||
					                 ((gbLcdMode == 3) && (gbLcdTicks > (GBLCD_MODE_3_CLOCK_TICKS - 2))) ||
					                 ((gbLcdMode == 0) && (gbLcdTicks <= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 2))))))
						return (gbMemory[address]);
					else
						return 0xff;
				}
				else
					return 0xff;
			case 0x70:
				if (gbCgbMode)
					return (0xf8 | register_SVBK);
				else
					return 0xff;
			case 0xff:
				return register_IE;
			}
		}
		// OAM not accessible during mode 2 & 3.
		if (((address >= 0xfe00) && (address < 0xfea0)) &&
		    ((gbLcdMode | gbLcdModeDelayed) & 2))
			return 0xff;
		break;
	}

	if ((address >= 0xfea0) && (address < 0xff00))
	{
		if (gbHardware & 1)
			return ((((address + ((address >> 4) - 0xfea)) >> 2) & 1) ? 0x00 : 0xff);
		else if (gbHardware & 2)
			return gbMemoryMap[address >> 12][address & 0x0fff];
		else if (gbHardware & 4)
			return ((((address + ((address >> 4) - 0xfea)) >> 2) & 1) ? 0xff : 0x00);
		else if (gbHardware & 8)
			return ((address & 0xf0) | ((address & 0xf0) >> 4));
	}

	return gbMemoryMap[address >> 12][address & 0x0fff];
}

u8 gbReadMemoryWrapped(register u16 address)
{
	if (gbCheatMap[address])
		return gbCheatRead(address);

	if (address < 0x8000)
		return gbMemoryMap[address >> 12][address & 0x0fff];

	if (address < 0xa000)
	{
		// A lot of 'ugly' checks... But only way to emulate this particular behaviour...
		if (
		    (
		        (gbHardware & 0xa) &&
		        (
		            (gbLcdModeDelayed != 3) ||
		            (
		                ((register_LY == 0) && (gbScreenOn == false) && (register_LCDC & 0x80)) &&
		                (gbLcdLYIncrementTicksDelayed == (GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS))
		            )
		        )
		    )
		    ||
		    (
		        (gbHardware & 0x5) &&
		        (gbLcdModeDelayed != 3) &&
		        (
		            (gbLcdMode != 3) ||
		            ((register_LY == 0) && ((gbScreenOn == false) && (register_LCDC & 0x80)) &&
		             (gbLcdLYIncrementTicks == (GBLY_INCREMENT_CLOCK_TICKS - GBLCD_MODE_2_CLOCK_TICKS)))
		        )
		    )
		    )
			return gbMemoryMap[address >> 12][address & 0x0fff];

		return 0xff;
	}

	if ((address >= 0xe000) && (address < 0xfe00))
		address &= ~0x2000;

	if (address < 0xc000)
	{
#ifndef FINAL_VERSION
		if (memorydebug)
		{
			log("Memory register read %04x PC=%04x\n",
			    address,
			    PC.W);
		}
#endif

		// for the 2kb ram limit (fixes crash in shawu's story
		// but now its sram test fails, as the it expects 8kb and not 2kb...
		// So use the 'genericflashcard' option to fix it).
		if (address <= (0xa000 + gbRamSizeMask))
		{
			if (mapperReadRAM)
				return mapperReadRAM(address);
			return gbMemoryMap[address >> 12][address & 0x0fff];
		}
		return 0xff;
	}

	if (address >= 0xff00)
	{
		if (address >= 0xFF10 && address <= 0xFF3F)
//			return gbSoundRead(address);
			return gbMemory[address];

		switch (address & 0x00ff)
		{
		case 0x00:
		{
			if (gbSgbMode)
			{
				gbSgbReadingController |= 4;
				gbSgbResetPacketState();
			}

			int b = gbMemory[0xff00];

			if ((b & 0x30) == 0x20)
			{
				b &= 0xf0;

				int joy = 0;
				if (gbSgbMode && gbSgbMultiplayer)
				{
					switch (gbSgbNextController)
					{
					case 0x0f:
						joy = 0;
						break;
					case 0x0e:
						joy = 1;
						break;
					case 0x0d:
						joy = 2;
						break;
					case 0x0c:
						joy = 3;
						break;
					default:
						joy = 0;
					}
				}
				int joystate = gbJoymask[joy];
				if (!(joystate & 128))
					b |= 0x08;
				if (!(joystate & 64))
					b |= 0x04;
				if (!(joystate & 32))
					b |= 0x02;
				if (!(joystate & 16))
					b |= 0x01;

				gbMemory[0xff00] = b;
			}
			else if ((b & 0x30) == 0x10)
			{
				b &= 0xf0;

				int joy = 0;
				if (gbSgbMode && gbSgbMultiplayer)
				{
					switch (gbSgbNextController)
					{
					case 0x0f:
						joy = 0;
						break;
					case 0x0e:
						joy = 1;
						break;
					case 0x0d:
						joy = 2;
						break;
					case 0x0c:
						joy = 3;
						break;
					default:
						joy = 0;
					}
				}
				int joystate = gbJoymask[joy];
				if (!(joystate & 8))
					b |= 0x08;
				if (!(joystate & 4))
					b |= 0x04;
				if (!(joystate & 2))
					b |= 0x02;
				if (!(joystate & 1))
					b |= 0x01;

				gbMemory[0xff00] = b;
			}
			else
			{
				if (gbSgbMode && gbSgbMultiplayer)
				{
					gbMemory[0xff00] = 0xf0 | gbSgbNextController;
				}
				else
				{
					gbMemory[0xff00] = 0xff;
				}
			}
		}
			systemCounters.lagged = false;
			return gbMemory[0xff00];
			break;
		case 0x01:
			return gbMemory[0xff01];
		case 0x02:
			return (gbMemory[0xff02]);
		case 0x04:
			return register_DIV;
		case 0x05:
			return register_TIMA;
		case 0x06:
			return register_TMA;
		case 0x07:
			return (0xf8 | register_TAC);
		case 0x0f:
			return (0xe0 | gbMemory[0xff0f]);
		case 0x30:
		case 0x31:
		case 0x32:
		case 0x33:
		case 0x34:
		case 0x35:
		case 0x36:
		case 0x37:
		case 0x38:
		case 0x39:
		case 0x3A:
		case 0x3B:
		case 0x3C:
		case 0x3D:
		case 0x3E:
		case 0x3F:
			if ((gbMemory[NR30] & 0x80) && (gbMemory[NR34] & 0x80))
				return 0xFF;
			else
				return gbMemoryMap[address >> 12][address & 0x0fff];
		case 0x40:
			return register_LCDC;
		case 0x41:
			// This is a GB/C only bug (ie. not GBA/SP).
			if ((gbHardware & 7) && (gbLcdMode == 2) && (gbLcdModeDelayed == 1) && (!gbSpeed))
				return (0x80 | (gbMemory[0xff41] & 0xFC));
			else
				return (0x80 | gbMemory[0xff41]);
		case 0x42:
			return register_SCY;
		case 0x43:
			return register_SCX;
		case 0x44:
			if (((gbHardware & 7) && ((gbLcdMode == 1) && (gbLcdTicks == 0x71))) ||
			    (!(register_LCDC & 0x80)))
				return (0);
			else
				return register_LY;
		case 0x45:
			return register_LYC;
		case 0x46:
			return register_DMA;
		case 0x4a:
			return register_WY;
		case 0x4b:
			return register_WX;
		case 0x4f:
			return (0xfe | register_VBK);
		case 0x51:
			return register_HDMA1;
		case 0x52:
			return register_HDMA2;
		case 0x53:
			return register_HDMA3;
		case 0x54:
			return register_HDMA4;
		case 0x55:
			return register_HDMA5;
		case 0x68:
		case 0x6a:
			if (gbCgbMode)
				return (0x40 | gbMemory[address]);
			else
				return 0xc0;
		case 0x69:
		case 0x6b:
			if (gbCgbMode)
			{
				// No access to gbPalette during mode 3 (Color Panel Demo)
				if (((gbLcdModeDelayed != 3) && (!((gbLcdMode == 0) && (gbLcdTicks >= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 1)))) && (!gbSpeed)) ||
				    (gbSpeed && ((gbLcdMode == 1) || (gbLcdMode == 2) ||
				                 ((gbLcdMode == 3) && (gbLcdTicks > (GBLCD_MODE_3_CLOCK_TICKS - 2))) ||
				                 ((gbLcdMode == 0) && (gbLcdTicks <= (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299] - 2))))))
					return (gbMemory[address]);
				else
					return 0xff;
			}
			else
				return 0xff;
		case 0x70:
			if (gbCgbMode)
				return (0xf8 | register_SVBK);
			else
				return 0xff;
		case 0xff:
			return register_IE;
		}
	}
	// OAM not accessible during mode 2 & 3.
	if (((address >= 0xfe00) && (address < 0xfea0)) &&
	    ((((gbLcdMode | gbLcdModeDelayed) & 2) &&
	      (!(gbSpeed && (gbHardware & 0x2) && !(gbLcdModeDelayed & 2) && (gbLcdMode == 2)))) ||
	     (gbSpeed && (gbHardware & 0x2) && (gbLcdModeDelayed == 0) && (gbLcdTicksDelayed == (GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299])))))
		return 0xff;

	if ((address >= 0xfea0) && (address < 0xff00))
	{
		if (gbHardware & 1)
			return ((((address + ((address >> 4) - 0xfea)) >> 2) & 1) ? 0x00 : 0xff);
		else if (gbHardware & 2)
			return gbMemoryMap[address >> 12][address & 0x0fff];
		else if (gbHardware & 4)
			return ((((address + ((address >> 4) - 0xfea)) >> 2) & 1) ? 0xff : 0x00);
		else if (gbHardware & 8)
			return ((address & 0xf0) | ((address & 0xf0) >> 4));
	}

	return gbMemoryMap[address >> 12][address & 0x0fff];
}

u8 gbReadMemory(register u16 address)
{
	u8 value = gbReadMemoryWrapped(address);
	CallRegisteredLuaMemHook(address, 1, value, LUAMEMHOOK_READ);
	return value;
}

void gbVblank_interrupt()
{
	gbCheatWrite(false); // Emulates GS codes.
	gbMemory[0xff0f] = register_IF &= 0xfe;
	gbWriteMemory(--SP.W, PC.B.B1);
	gbWriteMemory(--SP.W, PC.B.B0);
	PC.W = 0x40;
}

void gbLcd_interrupt()
{
	gbMemory[0xff0f] = register_IF &= 0xfd;
	gbWriteMemory(--SP.W, PC.B.B1);
	gbWriteMemory(--SP.W, PC.B.B0);
	PC.W = 0x48;
}

void gbTimer_interrupt()
{
	gbMemory[0xff0f] = register_IF &= 0xfb;
	gbWriteMemory(--SP.W, PC.B.B1);
	gbWriteMemory(--SP.W, PC.B.B0);
	PC.W = 0x50;
}

void gbSerial_interrupt()
{
	gbMemory[0xff0f] = register_IF &= 0xf7;
	gbWriteMemory(--SP.W, PC.B.B1);
	gbWriteMemory(--SP.W, PC.B.B0);
	PC.W = 0x58;
}

void gbJoypad_interrupt()
{
	gbMemory[0xff0f] = register_IF &= 0xef;
	gbWriteMemory(--SP.W, PC.B.B1);
	gbWriteMemory(--SP.W, PC.B.B0);
	PC.W = 0x60;
}

void gbSpeedSwitch()
{
	gbBlackScreen = true;
	if (gbSpeed == 0)
	{
		gbSpeed = 1;
		GBLCD_MODE_0_CLOCK_TICKS   = 51 * 2;
		GBLCD_MODE_1_CLOCK_TICKS   = 1140 * 2;
		GBLCD_MODE_2_CLOCK_TICKS   = 20 * 2;
		GBLCD_MODE_3_CLOCK_TICKS   = 43 * 2;
		GBLY_INCREMENT_CLOCK_TICKS = 114 * 2;
		GBDIV_CLOCK_TICKS = 64;
		GBTIMER_MODE_0_CLOCK_TICKS = 256;
		GBTIMER_MODE_1_CLOCK_TICKS = 4;
		GBTIMER_MODE_2_CLOCK_TICKS = 16;
		GBTIMER_MODE_3_CLOCK_TICKS = 64;
		GBSERIAL_CLOCK_TICKS	   = 128 * 2;
		gbLcdTicks		  *= 2;
		gbLcdTicksDelayed *= 2;
		gbLcdTicksDelayed--;
		gbLcdLYIncrementTicks		 *= 2;
		gbLcdLYIncrementTicksDelayed *= 2;
		gbLcdLYIncrementTicksDelayed--;
		gbSerialTicks *= 2;
		//USE_TICKS_AS = GB_SOUNDTICKS_AS * 2;
		//soundTicks *= 2;
		gbLine99Ticks = 3;
	}
	else
	{
		gbSpeed = 0;
		GBLCD_MODE_0_CLOCK_TICKS   = 51;
		GBLCD_MODE_1_CLOCK_TICKS   = 1140;
		GBLCD_MODE_2_CLOCK_TICKS   = 20;
		GBLCD_MODE_3_CLOCK_TICKS   = 43;
		GBLY_INCREMENT_CLOCK_TICKS = 114;
		GBDIV_CLOCK_TICKS = 64;
		GBTIMER_MODE_0_CLOCK_TICKS = 256;
		GBTIMER_MODE_1_CLOCK_TICKS = 4;
		GBTIMER_MODE_2_CLOCK_TICKS = 16;
		GBTIMER_MODE_3_CLOCK_TICKS = 64;
		GBSERIAL_CLOCK_TICKS	   = 128;
		gbLcdTicks >>= 1;
		gbLcdTicksDelayed++;
		gbLcdTicksDelayed	  >>= 1;
		gbLcdLYIncrementTicks >>= 1;
		gbLcdLYIncrementTicksDelayed++;
		gbLcdLYIncrementTicksDelayed >>= 1;
		gbSerialTicks /= 2;
		//USE_TICKS_AS = GB_SOUNDTICKS_AS;
		//soundTicks /= 2;
		gbLine99Ticks = 1;
		if (gbHardware & 8)
			gbLine99Ticks++;
	}
	gbDmaTicks += (134) * GBLY_INCREMENT_CLOCK_TICKS + (37 << (gbSpeed ? 1 : 0));
	//soundTickStep = USE_TICKS_AS * soundQuality;
}

void gbLoadInternalBios()
{
	// clear BIOS
	memset(bios, 0, 0x100);
}

void gbGetHardwareType()
{
	gbCgbMode = 0;
	gbSgbMode = 0;
	if (gbRom[0x143] & 0x80)
	{
		if ((gbEmulatorType == 0) ||
		    gbEmulatorType == 1 ||
		    gbEmulatorType == 4)
		{
			gbCgbMode = 1;
		}
	}

	if ((gbCgbMode == 0) && (gbRom[0x146] == 0x03))
	{
		if (gbEmulatorType == 0 ||
		    gbEmulatorType == 2 ||
		    gbEmulatorType == 5)
			gbSgbMode = 1;
	}

	gbHardware = 1; // GB
	if (((gbCgbMode == 1) && (gbEmulatorType == 0)) || (gbEmulatorType == 1))
		gbHardware = 2;  // GBC
	else if (((gbSgbMode == 1) && (gbEmulatorType == 0)) || (gbEmulatorType == 2) || (gbEmulatorType == 5))
		gbHardware = 4;  // SGB(2)
	else if (gbEmulatorType == 4)
		gbHardware = 8;  // GBA

	gbGBCColorType = 0;
	if (gbHardware & 8) // If GBA is selected, choose the GBA default settings.
		gbGBCColorType = 2;  // (0 = GBC, 1 = GBA, 2 = GBASP)
}

void gbReset()
{
	systemReset();

	gbGetHardwareType();
	// reset some settings if changed
	if (gbSpeed)
	{
		gbSpeedSwitch();
		gbMemory[0xff4d] = 0;
	}

	gbLYChangeHappened	  = false;
	gbLCDChangeHappened	  = false;
	gbRegisterLYLCDCOffOn = 0;	// unused
	inUseRegister_WY	  = 146;
	inUseRegister_WX	  = 192;

	gbLcdMode		  = 2;
	gbLcdModeDelayed  = 2;
	gbLcdTicks		  = GBLCD_MODE_2_CLOCK_TICKS - 1;
	gbLcdTicksDelayed = GBLCD_MODE_2_CLOCK_TICKS;
	gbLcdLYIncrementTicks		 = 114;
	gbLcdLYIncrementTicksDelayed = 115;
	gbScreenTicks				 = 0;

	gbInterruptWait = 0;
	gbDmaTicks		= 0;
	gbClockTicks		= 0;

	gbWhiteScreen	  = 0;
	gbBlackScreen	  = false;
	register_LCDCBusy = 0;

	gbRemainingClockTicks = 0;	// unused
	gbOldClockTicks		  = 0;
	gbIntBreak			  = 0;
	gbInterruptLaunched	  = 0;

	// hardware type specific things
	// div
	gbDivTicks = GBDIV_CLOCK_TICKS;
	// cgb
	gbVramBank = 0;
	gbWramBank = 1;
	//sgb
	gbSgbResetFlag = false;	// actually unused
	// gbHdmaDestination is 0x99d0 on startup (tested on HW)
	// but I'm not sure what gbHdmaSource is...
	gbHdmaSource	  = 0x99d0;
	gbHdmaDestination = 0x99d0;
	gbHdmaBytes		  = 0x0000;
	gbHdmaOn = 0;

	// clean Wram
	// This kinda emulates the startup state of Wram on GB/C (not very accurate,
	// but way closer to the reality than filling it with 00es or FFes).
	// On GBA/GBASP, it's kinda filled with random data.
	// In all cases, most of the 2nd bank is filled with 00s.
	// The starting data are important for some 'buggy' games, like Buster Brothers or
	// Karamuchou ha Oosawagi!.
	if (gbMemory != NULL)
	{
		memset(gbMemory, 0xff, 65536);
		for (int temp = 0xC000; temp < 0xE000; temp++)
			if ((temp & 0x8) ^ ((temp & 0x800) >> 8))
			{
				if ((gbHardware & 0x02) && (gbGBCColorType == 0))
					gbMemory[temp] = 0x0;
				else
					gbMemory[temp] = 0x0f;
			}

			else
				gbMemory[temp] = 0xff;
	}

	// GB bios set this memory area to 0
	// Fixes Pitman (J) title screen
	if (gbHardware & 0x1)
	{
		memset(&gbMemory[0x8000], 0x0, 0x2000);
	}

	// clean Vram and Wram 2
	if (gbCgbMode == 1)
	{
		if (gbVram == NULL)
			gbVram = (u8 *)RAM_MALLOC(0x4000);
		if (gbWram == NULL)
			gbWram = (u8 *)RAM_MALLOC(0x8000);
		memset(gbVram, 0, 0x4000);
		// This kinda emulates the startup state of Wram on GBC (not very accurate,
		// but way closer to the reality than filling it with 00es or FFes).
		// On GBA/GBASP, it's kinda filled with random data.
		// In all cases, most of the 2nd bank is filled with 00s.
		// The starting data are important for some 'buggy' games, like Buster Brothers or
		// Karamuchou ha Oosawagi!
		for (int i = 0; i < 8; i++)
			if (i != 2)
				memcpy((u16 *)(gbWram + i * 0x1000), (u16 *)(gbMemory + 0xC000), 0x1000);
	}
	else
	{
		free(gbVram);
		gbVram = NULL;
		free(gbWram);
		gbWram = NULL;
	}

	// clean LineBuffer
	if (gbLineBuffer != NULL)
		memset(gbLineBuffer, 0, 160 * sizeof(u16));
	// clean Pix
	if (pix != NULL)
		memset(pix, 0, 4 * 257 * 226);

	memset(gbSCYLine, 0, sizeof(gbSCYLine));
	memset(gbSCXLine, 0, sizeof(gbSCXLine));
	memset(gbBgpLine, 0xfc, sizeof(gbBgpLine));
	if (gbHardware & 5)
	{
		memset(gbObp0Line, 0xff, sizeof(gbObp0Line));
		memset(gbObp1Line, 0xff, sizeof(gbObp1Line));
	}
	else
	{
		memset(gbObp0Line, 0x0, sizeof(gbObp0Line));
		memset(gbObp1Line, 0x0, sizeof(gbObp1Line));
	}
	memset(gbSpritesTicks, 0x0, sizeof(gbSpritesTicks));

	SP.W = 0xfffe;
	AF.W = 0x01b0;
	BC.W = 0x0013;
	DE.W = 0x00d8;
	HL.W = 0x014d;
	PC.W = 0x0100;
	IFF	 = 0;
	gbInt48Signal = 0;

	register_TIMA	 = 0;
	register_TMA	 = 0;
	register_TAC	 = 0;
	gbMemory[0xff0f] = register_IF = 1;
	gbMemory[0xff40] = register_LCDC = 0x91;
	gbMemory[0xff47] = 0xfc;

	if (gbCgbMode)
		gbMemory[0xff4d] = 0x7e;
	else
		gbMemory[0xff4d] = 0xff;

	if (!gbCgbMode)
		gbMemory[0xff70] = gbMemory[0xff74] = 0xff;

	if (gbCgbMode)
		gbMemory[0xff56] = 0x3e;
	else
		gbMemory[0xff56] = 0xff;

	register_SCY   = 0;
	register_SCX   = 0;
	register_LYC   = 0;
	register_DMA   = 0xff;
	register_WY	   = 0;
	register_WX	   = 0;
	register_VBK   = 0;
	register_HDMA1 = 0xff;
	register_HDMA2 = 0xff;
	register_HDMA3 = 0xff;
	register_HDMA4 = 0xff;
	register_HDMA5 = 0xff;
	register_SVBK  = 0;
	register_IE	   = 0;

	if (gbCgbMode)
		gbMemory[0xff02] = 0x7c;
	else
		gbMemory[0xff02] = 0x7e;

	gbMemory[0xff03] = 0xff;

	for (int i = 0x8; i < 0xf; i++)
		gbMemory[0xff00 + i] = 0xff;

	gbMemory[0xff13] = 0xff;
	gbMemory[0xff15] = 0xff;
	gbMemory[0xff18] = 0xff;
	gbMemory[0xff1d] = 0xff;
	gbMemory[0xff1f] = 0xff;

	for (int i = 0x27; i < 0x30; i++)
		gbMemory[0xff00 + i] = 0xff;

	gbMemory[0xff4c] = 0xff;
	gbMemory[0xff4e] = 0xff;
	gbMemory[0xff50] = 0xff;

	for (int i = 0x57; i < 0x68; i++)
		gbMemory[0xff00 + i] = 0xff;

	for (int i = 0x5d; i < 0x70; i++)
		gbMemory[0xff00 + i] = 0xff;

	gbMemory[0xff71] = 0xff;

	for (int i = 0x78; i < 0x80; i++)
		gbMemory[0xff00 + i] = 0xff;

	if (gbHardware & 0xa)
	{
		if (gbHardware & 2)
		{
			AF.W = 0x1180;
			BC.W = 0x0000;
		}
		else
		{
			AF.W = 0x1100;
			BC.W = 0x0100; // GBA/SP have B = 0x01 (which means GBC & GBA/SP bootrom are different !)
		}

		gbMemory[0xff26] = 0xf1;
		if (gbCgbMode)
		{
			gbMemory[0xff31] = 0xff;
			gbMemory[0xff33] = 0xff;
			gbMemory[0xff35] = 0xff;
			gbMemory[0xff37] = 0xff;
			gbMemory[0xff39] = 0xff;
			gbMemory[0xff3b] = 0xff;
			gbMemory[0xff3d] = 0xff;

			gbMemory[0xff44] = register_LY = 0x90;
			gbDivTicks		 = 0x19 + ((gbHardware & 2) >> 1);
			gbInternalTimer	 = 0x58 + ((gbHardware & 2) >> 1);
			gbLcdTicks		 = GBLCD_MODE_1_CLOCK_TICKS -
			                   (register_LY - 0x8F) * GBLY_INCREMENT_CLOCK_TICKS + 72 + ((gbHardware & 2) >> 1);
			gbLcdLYIncrementTicks = 72 + ((gbHardware & 2) >> 1);
			gbMemory[0xff04]	  = register_DIV = 0x1E;
		}
		else
		{
			gbMemory[0xff44] = register_LY = 0x94;
			gbDivTicks		 = 0x22 + ((gbHardware & 2) >> 1);
			gbInternalTimer	 = 0x61 + ((gbHardware & 2) >> 1);
			gbLcdTicks		 = GBLCD_MODE_1_CLOCK_TICKS -
			                   (register_LY - 0x8F) * GBLY_INCREMENT_CLOCK_TICKS + 25 + ((gbHardware & 2) >> 1);
			gbLcdLYIncrementTicks = 25 + ((gbHardware & 2) >> 1);
			gbMemory[0xff04]	  = register_DIV = 0x26;
		}

		DE.W = 0xff56;
		HL.W = 0x000d;

		register_HDMA5	 = 0xff;
		gbMemory[0xff68] = 0xc0;
		gbMemory[0xff6a] = 0xc0;

		gbMemory[0xff41] = register_STAT = 0x81;
		gbLcdMode		 = 1;
	}
	else
	{
		if (gbHardware & 4)
		{
			if (gbEmulatorType == 5)
				AF.W = 0xffb0;
			else
				AF.W = 0x01b0;
			BC.W = 0x0013;
			DE.W = 0x00d8;
			HL.W = 0x014d;
		}
		gbDivTicks = 14;
		gbInternalTimer		  = gbDivTicks--;
		gbMemory[0xff04]	  = register_DIV = 0xAB;
		gbMemory[0xff41]	  = register_STAT = 0x85;
		gbMemory[0xff44]	  = register_LY = 0x00;
		gbLcdTicks			  = 15;
		gbLcdLYIncrementTicks = 114 + gbLcdTicks;
		gbLcdMode = 1;

		// used for the handling of the gb Boot Rom
		if ((gbHardware & 5) && (bios != NULL) && useBios && !skipBios)
		{
			memcpy((u8 *)(gbMemory), (u8 *)(gbRom), 0x1000);
			memcpy((u8 *)(gbMemory), (u8 *)(bios), 0x100);
			gbWhiteScreen = 0;

			gbInternalTimer	 = 0x3e;
			gbDivTicks		 = 0x3f;
			gbMemory[0xff04] = register_DIV = 0x00;
			PC.W = 0x0000;
			register_LCDC		  = 0x11;
			gbScreenOn			  = false;
			gbLcdTicks			  = 0;
			gbLcdMode			  = 0;
			gbLcdModeDelayed	  = 0;
			gbMemory[0xff41]	  = register_STAT &= 0xfc;
			gbInt48Signal		  = 0;
			gbLcdLYIncrementTicks = GBLY_INCREMENT_CLOCK_TICKS;
		}
	}

	gbLine99Ticks = 1;
	if (gbHardware & 8)
		gbLine99Ticks++;

	gbLcdModeDelayed  = gbLcdMode;
	gbLcdTicksDelayed = gbLcdTicks + 1;
	gbLcdLYIncrementTicksDelayed = gbLcdLYIncrementTicks + 1;

	gbTimerModeChange = false;
	gbTimerOnChange	  = false;
	gbTimerOn		  = false;

	memset(gbPalette, 0, 2 * 128);

	if (gbCgbMode)
	{
		for (int i = 0; i < 0x20; i++)
			gbPalette[i] = 0x7fff;

		// This is just to show that the starting values of the OBJ palettes are different
		// between the 3 consoles, and that they 'kinda' stay the same at each reset
		// (they can slightly change, somehow (randomly?)).
		// You can check the effects of gbGBCColorType on the "Vila Caldan Color" gbc demo.
		// Note that you could also check the Div register to check on which system the game
		// is running (GB,GBC and GBA(SP) have different startup values).
		// Unfortunatly, I don't have any SGB system, so I can't get their starting values.

		if (gbGBCColorType == 0) // GBC Hardware
		{
			gbPalette[0x20] = 0x0600;
			gbPalette[0x21] = 0xfdf3;
			gbPalette[0x22] = 0x041c;
			gbPalette[0x23] = 0xf5db;
			gbPalette[0x24] = 0x4419;
			gbPalette[0x25] = 0x57ea;
			gbPalette[0x26] = 0x2808;
			gbPalette[0x27] = 0x9b75;
			gbPalette[0x28] = 0x129b;
			gbPalette[0x29] = 0xfce0;
			gbPalette[0x2a] = 0x22da;
			gbPalette[0x2b] = 0x4ac5;
			gbPalette[0x2c] = 0x2d71;
			gbPalette[0x2d] = 0xf0c2;
			gbPalette[0x2e] = 0x5137;
			gbPalette[0x2f] = 0x2d41;
			gbPalette[0x30] = 0x6b2d;
			gbPalette[0x31] = 0x2215;
			gbPalette[0x32] = 0xbe0a;
			gbPalette[0x33] = 0xc053;
			gbPalette[0x34] = 0xfe5f;
			gbPalette[0x35] = 0xe000;
			gbPalette[0x36] = 0xbe10;
			gbPalette[0x37] = 0x914d;
			gbPalette[0x38] = 0x7f91;
			gbPalette[0x39] = 0x02b5;
			gbPalette[0x3a] = 0x77ac;
			gbPalette[0x3b] = 0x14e5;
			gbPalette[0x3c] = 0xcf89;
			gbPalette[0x3d] = 0xa03d;
			gbPalette[0x3e] = 0xfd50;
			gbPalette[0x3f] = 0x91ff;
		}
		else if (gbGBCColorType == 1) // GBA Hardware
		{
			gbPalette[0x20] = 0xbe00;
			gbPalette[0x21] = 0xfdfd;
			gbPalette[0x22] = 0xbd69;
			gbPalette[0x23] = 0x7baf;
			gbPalette[0x24] = 0xf5ff;
			gbPalette[0x25] = 0x3f8f;
			gbPalette[0x26] = 0xcee5;
			gbPalette[0x27] = 0x5bf7;
			gbPalette[0x28] = 0xb35b;
			gbPalette[0x29] = 0xef97;
			gbPalette[0x2a] = 0xef9f;
			gbPalette[0x2b] = 0x97f7;
			gbPalette[0x2c] = 0x82bf;
			gbPalette[0x2d] = 0x9f3d;
			gbPalette[0x2e] = 0xddde;
			gbPalette[0x2f] = 0xbad5;
			gbPalette[0x30] = 0x3cba;
			gbPalette[0x31] = 0xdfd7;
			gbPalette[0x32] = 0xedea;
			gbPalette[0x33] = 0xfeda;
			gbPalette[0x34] = 0xf7f9;
			gbPalette[0x35] = 0xfdee;
			gbPalette[0x36] = 0x6d2f;
			gbPalette[0x37] = 0xf0e6;
			gbPalette[0x38] = 0xf7f0;
			gbPalette[0x39] = 0xf296;
			gbPalette[0x3a] = 0x3bf1;
			gbPalette[0x3b] = 0xe211;
			gbPalette[0x3c] = 0x69ba;
			gbPalette[0x3d] = 0x3d0d;
			gbPalette[0x3e] = 0xdfd3;
			gbPalette[0x3f] = 0xa6ba;
		}
		else if (gbGBCColorType == 2) // GBASP Hardware
		{
			gbPalette[0x20] = 0x9c00;
			gbPalette[0x21] = 0x6340;
			gbPalette[0x22] = 0x10c6;
			gbPalette[0x23] = 0xdb97;
			gbPalette[0x24] = 0x7622;
			gbPalette[0x25] = 0x3e57;
			gbPalette[0x26] = 0x2e12;
			gbPalette[0x27] = 0x95c3;
			gbPalette[0x28] = 0x1095;
			gbPalette[0x29] = 0x488c;
			gbPalette[0x2a] = 0x8241;
			gbPalette[0x2b] = 0xde8c;
			gbPalette[0x2c] = 0xfabc;
			gbPalette[0x2d] = 0x0e81;
			gbPalette[0x2e] = 0x7675;
			gbPalette[0x2f] = 0xfdec;
			gbPalette[0x30] = 0xddfd;
			gbPalette[0x31] = 0x5995;
			gbPalette[0x32] = 0x066a;
			gbPalette[0x33] = 0xed1e;
			gbPalette[0x34] = 0x1e84;
			gbPalette[0x35] = 0x1d14;
			gbPalette[0x36] = 0x11c3;
			gbPalette[0x37] = 0x2749;
			gbPalette[0x38] = 0xa727;
			gbPalette[0x39] = 0x6266;
			gbPalette[0x3a] = 0xe27b;
			gbPalette[0x3b] = 0xe3fc;
			gbPalette[0x3c] = 0x1f76;
			gbPalette[0x3d] = 0xf158;
			gbPalette[0x3e] = 0x468e;
			gbPalette[0x3f] = 0xa540;
		}
	}
	else
	{
		if (gbSgbMode)
		{
			for (int i = 0; i < 8; i++)
				gbPalette[i] = systemGbPalette[gbPaletteOption * 8 + i];
		}
		for (int i = 0; i < 8; i++)
			gbPalette[i] = systemGbPalette[gbPaletteOption * 8 + i];
	}

	GBTIMER_MODE_0_CLOCK_TICKS = 256;
	GBTIMER_MODE_1_CLOCK_TICKS = 4;
	GBTIMER_MODE_2_CLOCK_TICKS = 16;
	GBTIMER_MODE_3_CLOCK_TICKS = 64;

	GBLY_INCREMENT_CLOCK_TICKS = 114;
	gbTimerTicks	  = GBTIMER_MODE_0_CLOCK_TICKS;
	gbTimerClockTicks = GBTIMER_MODE_0_CLOCK_TICKS;
	gbSerialTicks	  = 0;
	gbSerialBits	  = 0;
	gbSerialOn		  = 0;
	gbWindowLine	  = -1;
	gbTimerOn		  = false;
	gbTimerMode		  = 0;
	gbSpeed			  = 0;
	gbJoymask[0]	  = gbJoymask[1] = gbJoymask[2] = gbJoymask[3] = 0;

	if (gbCgbMode)
	{
		gbHdmaOn = 0;
		gbHdmaSource	  = 0x99d0;
		gbHdmaDestination = 0x99d0;
		gbVramBank		  = 0;
		gbWramBank		  = 1;
	}

	// used to clean the borders
	if (gbSgbMode)
	{
//		gbSgbResetFlag = true;
		gbSgbReset();
//		if (gbBorderOn)
//			gbSgbRenderBorder();
//		gbSgbResetFlag = false;
	}

	for (int i = 0; i < 4; i++)
		gbBgp[i] = gbObp0[i] = gbObp1[i] = i;

	memset(&gbDataMBC1, 0, sizeof(gbDataMBC1));
	gbDataMBC1.mapperROMBank = 1;

	gbDataMBC2.mapperRAMEnable = 0;
	gbDataMBC2.mapperROMBank   = 1;

	memset(&gbDataMBC3, 0, 6 * sizeof(int32));
	gbDataMBC3.mapperROMBank = 1;

	memset(&gbDataMBC5, 0, sizeof(gbDataMBC5));
	gbDataMBC5.mapperROMBank = 1;
	switch (gbRom[0x147])
	{
	case 0x1c:
	case 0x1d:
	case 0x1e:
		gbDataMBC5.isRumbleCartridge = 1;
	}

	memset(&gbDataHuC1, 0, sizeof(gbDataHuC1));
	gbDataHuC1.mapperROMBank = 1;

	memset(&gbDataHuC3, 0, sizeof(gbDataHuC3));
	gbDataHuC3.mapperROMBank = 1;

	memset(&gbDataTAMA5, 0, 26 * sizeof(int32));
	gbDataTAMA5.mapperROMBank = 1;

	memset(&gbDataMMM01, 0, sizeof(gbDataMMM01));
	gbDataMMM01.mapperROMBank = 1;

	if (useBios && !skipBios && (gbHardware & 5))
	{
		gbMemoryMap[0x00] = &gbMemory[0x0000];
		inBios = true;
	}
	else
	{
		gbMemoryMap[0x00] = &gbRom[0x0000];
		inBios = false;
	}

	gbMemoryMap[0x01] = &gbRom[0x1000];
	gbMemoryMap[0x02] = &gbRom[0x2000];
	gbMemoryMap[0x03] = &gbRom[0x3000];
	gbMemoryMap[0x04] = &gbRom[0x4000];
	gbMemoryMap[0x05] = &gbRom[0x5000];
	gbMemoryMap[0x06] = &gbRom[0x6000];
	gbMemoryMap[0x07] = &gbRom[0x7000];
	if (gbCgbMode)
	{
		gbMemoryMap[0x08] = &gbVram[0x0000];
		gbMemoryMap[0x09] = &gbVram[0x1000];
		gbMemoryMap[0x0a] = &gbMemory[0xa000];
		gbMemoryMap[0x0b] = &gbMemory[0xb000];
		gbMemoryMap[0x0c] = &gbMemory[0xc000];
		gbMemoryMap[0x0d] = &gbWram[0x1000];
		gbMemoryMap[0x0e] = &gbMemory[0xe000];
		gbMemoryMap[0x0f] = &gbMemory[0xf000];
	}
	else
	{
		gbMemoryMap[0x08] = &gbMemory[0x8000];
		gbMemoryMap[0x09] = &gbMemory[0x9000];
		gbMemoryMap[0x0a] = &gbMemory[0xa000];
		gbMemoryMap[0x0b] = &gbMemory[0xb000];
		gbMemoryMap[0x0c] = &gbMemory[0xc000];
		gbMemoryMap[0x0d] = &gbMemory[0xd000];
		gbMemoryMap[0x0e] = &gbMemory[0xe000];
		gbMemoryMap[0x0f] = &gbMemory[0xf000];
	}

	if (gbRam)
	{
		gbMemoryMap[0x0a] = &gbRam[0x0000];
		gbMemoryMap[0x0b] = &gbRam[0x1000];
	}

	gbScreenOn		= true;
	gbSystemMessage = false;

	gbCheatWrite(true); // Emulates GS codes.

	// timing
	stopCounter = 0;
	gbSynchronizeTicks = GBSYNCHRONIZE_CLOCK_TICKS;

	gbSoundReset();
	systemRefreshScreen();
}

void gbWriteSaveMBC1(const char *name)
{
	if (gbRam)
	{
		FILE *gzFile = fopen(name, "wb");

		if (gzFile == NULL)
		{
			systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
			return;
		}

		fwrite(gbRam,
		       1,
		       (gbRamSizeMask + 1),
		       gzFile);

		fclose(gzFile);
	}
}

void gbWriteSaveMBC2(const char *name)
{
	if (gbRam)
	{
		FILE *file = fopen(name, "wb");

		if (file == NULL)
		{
			systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
			return;
		}

		fwrite(gbMemoryMap[0x0a],
		       1,
		       512,
		       file);

		fclose(file);
	}
}

void gbWriteSaveMBC3(const char *name, bool extendedSave)
{
	if (gbRam || extendedSave)
	{
		FILE *gzFile = fopen(name, "wb");
		if (gbRam)
		{
			if (gzFile == NULL)
			{
				systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
				return;
			}

			fwrite(gbRam,
			       1,
			       (gbRamSizeMask + 1),
			       gzFile);
		}

		if (extendedSave)
			fwrite(&gbDataMBC3.mapperSeconds,
			       1,
			       10 * sizeof(int32) + sizeof(u32),
			       gzFile);

		fclose(gzFile);
	}
}

void gbWriteSaveMBC5(const char *name)
{
	if (gbRam)
	{
		FILE *gzFile = fopen(name, "wb");

		if (gzFile == NULL)
		{
			systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
			return;
		}

		fwrite(gbRam,
		       1,
		       (gbRamSizeMask + 1),
		       gzFile);

		fclose(gzFile);
	}
}

void gbWriteSaveMBC7(const char *name)
{
	if (gbRam)
	{
		FILE *file = fopen(name, "wb");

		if (file == NULL)
		{
			systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
			return;
		}

		fwrite(&gbMemory[0xa000],
		       1,
		       256,
		       file);

		fclose(file);
	}
}

void gbWriteSaveTAMA5(const char *name, bool extendedSave)
{
	FILE *gzFile = fopen(name, "wb");

	if (gzFile == NULL)
	{
		systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
		return;
	}
	if (gbRam)
		fwrite(gbRam,
		       1,
		       (gbRamSizeMask + 1),
		       gzFile);

	fwrite(gbTAMA5ram,
	       1,
	       (gbTAMA5ramSize),
	       gzFile);

	if (extendedSave)
		fwrite(&gbDataTAMA5.mapperSeconds,
		       1,
		       14 * sizeof(int32) + sizeof(u32),
		       gzFile);

	fclose(gzFile);
}

void gbWriteSaveMMM01(const char *name)
{
	if (gbRam)
	{
		FILE *gzFile = fopen(name, "wb");

		if (gzFile == NULL)
		{
			systemMessage(MSG_ERROR_CREATING_FILE, N_("Error creating file %s"), name);
			return;
		}

		fwrite(gbRam,
		       1,
		       (gbRamSizeMask + 1),
		       gzFile);

		fclose(gzFile);
	}
}

bool gbReadSaveMBC1(const char *name)
{
	if (gbRam)
	{
		gzFile gzFile = gzopen(name, "rb");

		if (gzFile == NULL)
		{
			return false;
		}

		int read = gzread(gzFile,
		                  gbRam,
		                  (gbRamSizeMask + 1));

		if (read != (gbRamSizeMask + 1))
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		// Also checks if the battery file it bigger than gbRamSizeMask+1 !
		u8 data[1];
		data[0] = 0;

		read = gzread(gzFile,
		              data,
		              1);
		if (read > 0)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		gzclose(gzFile);
		return true;
	}
	else
		return false;
}

bool gbReadSaveMBC2(const char *name)
{
	if (gbRam)
	{
		FILE *file = fopen(name, "rb");

		if (file == NULL)
		{
			return false;
		}

		size_t read = fread(gbMemoryMap[0x0a],
		                    1,
		                    512,
		                    file);

		if (read != 512)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			fclose(file);
			gbBatteryError = true;
			return false;
		}

		// Also checks if the battery file it bigger than gbRamSizeMask+1 !
		u8 data[1];
		data[0] = 0;

		read = fread(&data[0],
		             1,
		             1,
		             file);
		if (read > 0)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			fclose(file);
			gbBatteryError = true;
			return false;
		}

		fclose(file);
		return true;
	}
	else
		return false;
}

bool gbReadSaveMBC3(const char *name)
{
	gzFile gzFile = gzopen(name, "rb");

	if (gzFile == NULL)
	{
		return false;
	}

	int read = 0;

	if (gbRam)
		read = gzread(gzFile,
		              gbRam,
		              (gbRamSizeMask + 1));
	else
		read = (gbRamSizeMask + 1);

	bool res = true;

	if (read != (gbRamSizeMask + 1))
	{
		systemMessage(MSG_FAILED_TO_READ_SGM,
		              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
						  "Warning : save of the battery file is now disabled !"),
					  name, read);
		gbBatteryError = true;
		res = false;
	}
	else if ((gbRomType == 0xf) || (gbRomType == 0x10))
	{
		read = gzread(gzFile,
		              &gbDataMBC3.mapperSeconds,
		              sizeof(int32) * 10 + sizeof(u32));

		if (read != (sizeof(int32) * 10 + sizeof(u32)) && read != 0)
		{
			systemMessage(MSG_FAILED_TO_READ_RTC,
			              N_("Failed to read RTC from save game %s (continuing)"),
			              name);
			res = false;
		}
		else if (read == 0)
		{
			systemMessage(MSG_FAILED_TO_READ_RTC, N_("Failed to read RTC from save game %s (continuing)"),
			              name);
			res = false;
		}
		else
		{
			// Also checks if the battery file it bigger than gbRamSizeMask+1+RTC !
			u8 data[1];
			data[0] = 0;

			read = gzread(gzFile,
			              data,
			              1);
			if (read > 0)
			{
				systemMessage(MSG_FAILED_TO_READ_SGM,
				              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
								  "Warning : save of the battery file is now disabled !"),
				              name, read);
				gbBatteryError = true;
				res = false;
			}
		}
	}

	gzclose(gzFile);
	return res;
}

bool gbReadSaveMBC5(const char *name)
{
	if (gbRam)
	{
		gzFile gzFile = gzopen(name, "rb");

		if (gzFile == NULL)
		{
			return false;
		}

		int read = gzread(gzFile,
		                  gbRam,
		                  (gbRamSizeMask + 1));

		if (read != (gbRamSizeMask + 1))
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		// Also checks if the battery file it bigger than gbRamSizeMask+1 !
		u8 data[1];
		data[0] = 0;

		read = gzread(gzFile,
		              data,
		              1);
		if (read > 0)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		gzclose(gzFile);
		return true;
	}
	else
		return false;
}

bool gbReadSaveMBC7(const char *name)
{
	if (gbRam)
	{
		FILE *file = fopen(name, "rb");

		if (file == NULL)
		{
			return false;
		}

		size_t read = fread(&gbMemory[0xa000],
		                    1,
		                    256,
		                    file);

		if (read != 256)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			fclose(file);
			gbBatteryError = true;
			return false;
		}

		// Also checks if the battery file it bigger than gbRamSizeMask+1 !
		u8 data[1];
		data[0] = 0;

		read = fread(&data[0],
		             1,
		             1,
		             file);
		if (read > 0)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			fclose(file);
			gbBatteryError = true;
			return false;
		}

		fclose(file);
		return true;
	}
	else
		return false;
}

bool gbReadSaveTAMA5(const char *name)
{
	gzFile gzFile = gzopen(name, "rb");

	if (gzFile == NULL)
	{
		return false;
	}

	int read = 0;

	if (gbRam)
		read = gzread(gzFile,
		              gbRam,
		              (gbRamSizeMask + 1));
	else
		read = gbRamSizeMask;

	read += gzread(gzFile,
	               gbTAMA5ram,
	               gbTAMA5ramSize);

	bool res = true;

	if (read != (gbRamSizeMask + gbTAMA5ramSize + 1))
	{
		systemMessage(MSG_FAILED_TO_READ_SGM,
		              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
						  "Warning : save of the battery file is now disabled !"),
					  name, read);
		gbBatteryError = true;
		res = false;
	}
	else
	{
		read = gzread(gzFile,
		              &gbDataTAMA5.mapperSeconds,
		              sizeof(int32) * 14 + sizeof(u32));

		if (read != (sizeof(int32) * 14 + sizeof(u32)) && read != 0)
		{
			systemMessage(MSG_FAILED_TO_READ_RTC, N_("Failed to read RTC from save game %s (continuing)"),
			              name);
			res = false;
		}
		else if (read == 0)
		{
			systemMessage(MSG_FAILED_TO_READ_RTC, N_("Failed to read RTC from save game %s (continuing)"),
			              name);
			res = false;
		}
		else
		{
			// Also checks if the battery file it bigger than gbRamSizeMask+1+RTC !
			u8 data[1];
			data[0] = 0;

			read = gzread(gzFile,
			              data,
			              1);
			if (read > 0)
			{
				systemMessage(MSG_FAILED_TO_READ_SGM,
				              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
								  "Warning : save of the battery file is now disabled !"),
				              name, read);
				gbBatteryError = true;
				res = false;
			}
		}
	}

	gzclose(gzFile);
	return res;
}

bool gbReadSaveMMM01(const char *name)
{
	if (gbRam)
	{
		gzFile gzFile = gzopen(name, "rb");

		if (gzFile == NULL)
		{
			return false;
		}

		int read = gzread(gzFile,
		                  gbRam,
		                  (gbRamSizeMask + 1));

		if (read != (gbRamSizeMask + 1))
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
						  name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		// Also checks if the battery file it bigger than gbRamSizeMask+1 !
		u8 data[1];
		data[0] = 0;

		read = gzread(gzFile,
		              data,
		              1);
		if (read > 0)
		{
			systemMessage(MSG_FAILED_TO_READ_SGM,
			              N_("Battery file's size incompatible with the rom settings %s (%d).\n"
							  "Warning : save of the battery file is now disabled !"),
			              name, read);
			gzclose(gzFile);
			gbBatteryError = true;
			return false;
		}

		gzclose(gzFile);
		return true;
	}
	else
		return false;
}

void gbInit()
{
	gbGenFilter();
	gbSgbInit();    // calls gbSgbReset()... whatever

	gbMemory = (u8 *)RAM_MALLOC(0x10000);

	pix = (u8 *)PIX_CALLOC(4 * 257 * 226);

	gbLineBuffer = (u16 *)malloc(160 * sizeof(u16));

	free(bios);
	bios = (u8 *)calloc(1, 0x100);
}

bool gbWriteBatteryFile(const char *file, bool extendedSave)
{
	if (gbBattery)
	{
		switch (gbRomType)
		{
		case 0x03:
			gbWriteSaveMBC1(file);
			break;
		case 0x06:
			gbWriteSaveMBC2(file);
			break;
		case 0x0d:
			gbWriteSaveMMM01(file);
			break;
		case 0x0f:
		case 0x10:
			gbWriteSaveMBC3(file, extendedSave);
			break;
		case 0x13:
		case 0xfc:
			gbWriteSaveMBC3(file, false);
		case 0x1b:
		case 0x1e:
			gbWriteSaveMBC5(file);
			break;
		case 0x22:
			gbWriteSaveMBC7(file);
			break;
		case 0xfd:
			gbWriteSaveTAMA5(file, extendedSave);
			break;
		case 0xff:
			gbWriteSaveMBC1(file);
			break;
		}
	}
	return true;
}

bool gbWriteBatteryFile(const char *file)
{
	if (!gbBatteryError)
	{
		gbWriteBatteryFile(file, true);
		return true;
	}
	else return false;
}

bool gbReadBatteryFile(const char *file)
{
	bool res = false;
	if (gbBattery)
	{
		switch (gbRomType)
		{
		case 0x03:
			res = gbReadSaveMBC1(file);
			break;
		case 0x06:
			res = gbReadSaveMBC2(file);
			break;
		case 0x0d:
			res = gbReadSaveMMM01(file);
			break;
		case 0x0f:
		case 0x10:
			if (!gbReadSaveMBC3(file))
			{
				struct tm *lt;
				time_t tmp; //64 bit kludge
				if (VBAMovieIsActive() || VBAMovieIsLoading())
				{
					tmp = time_t(VBAMovieGetId() + VBAMovieGetFrameCounter() / 60);
					lt  = gmtime(&tmp);
				}
				else
				{
					tmp = time(NULL);
					lt  = localtime(&tmp);
				}
				systemScreenMessage(ctime(&tmp), 4);
				gbDataMBC3.mapperLastTime = u32(tmp);

				gbDataMBC3.mapperSeconds = lt->tm_sec;
				gbDataMBC3.mapperMinutes = lt->tm_min;
				gbDataMBC3.mapperHours	 = lt->tm_hour;
				gbDataMBC3.mapperDays	 = lt->tm_yday & 255;
				gbDataMBC3.mapperControl = (gbDataMBC3.mapperControl & 0xfe) |
				                           (lt->tm_yday > 255 ? 1 : 0);
				res = false;
				break;
			}
			res = true;
			break;
		case 0x13:
		case 0xfc:
			res = gbReadSaveMBC3(file);
			break;
		case 0x1b:
		case 0x1e:
			res = gbReadSaveMBC5(file);
			break;
		case 0x22:
			res = gbReadSaveMBC7(file);
			break;
		case 0xfd:
			if (!gbReadSaveTAMA5(file))
			{
				u8 gbDaysinMonth [12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
				struct tm *lt;
				time_t tmp; //64 bit kludge
				if (VBAMovieIsActive() || VBAMovieIsLoading())
				{
					tmp = time_t(VBAMovieGetId() + VBAMovieGetFrameCounter() / 60);
					lt  = gmtime(&tmp);
				}
				else
				{
					tmp = time(NULL);
					lt  = localtime(&tmp);
				}
				systemScreenMessage(ctime(&tmp), 4);
				gbDataTAMA5.mapperLastTime = (u32)tmp;

				gbDataTAMA5.mapperSeconds = lt->tm_sec;
				gbDataTAMA5.mapperMinutes = lt->tm_min;
				gbDataTAMA5.mapperHours	  = lt->tm_hour;
				gbDataTAMA5.mapperDays	  = 1;
				gbDataTAMA5.mapperMonths  = 1;
				gbDataTAMA5.mapperYears	  = 1970;
				int days = lt->tm_yday + 365 * 3;
				while (days)
				{
					gbDataTAMA5.mapperDays++;
					days--;
					if (gbDataTAMA5.mapperDays > gbDaysinMonth[gbDataTAMA5.mapperMonths - 1])
					{
						gbDataTAMA5.mapperDays = 1;
						gbDataTAMA5.mapperMonths++;
						if (gbDataTAMA5.mapperMonths > 12)
						{
							gbDataTAMA5.mapperMonths = 1;
							gbDataTAMA5.mapperYears++;
							if ((gbDataTAMA5.mapperYears & 3) == 0)
								gbDaysinMonth[1] = 29;
							else
								gbDaysinMonth[1] = 28;
						}
					}
				}
				gbDataTAMA5.mapperControl = (gbDataTAMA5.mapperControl & 0xfe) |
				                            (lt->tm_yday > 255 ? 1 : 0);
				res = false;
				break;
			}
			res = true;
			break;
		case 0xff:
			res = gbReadSaveMBC1(file);
			break;
		}
	}
	systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
	return res;
}

bool gbReadGSASnapshot(const char *fileName)
{
	FILE *file = fopen(fileName, "rb");

	if (!file)
	{
		systemMessage(MSG_CANNOT_OPEN_FILE, N_("Cannot open file %s"), fileName);
		return false;
	}

	fseek(file, 0x4, SEEK_SET);
	char buffer[16];
	char buffer2[16];
	fread(buffer, 1, 15, file);
	buffer[15] = 0;
	memcpy(buffer2, &gbRom[0x134], 15);
	buffer2[15] = 0;
	if (memcmp(buffer, buffer2, 15))
	{
		systemMessage(MSG_CANNOT_IMPORT_SNAPSHOT_FOR,
		              N_("Cannot import snapshot for %s. Current game is %s"),
		              buffer,
		              buffer2);
		fclose(file);
		return false;
	}
	fseek(file, 0x13, SEEK_SET);
	size_t read	  = 0;
	int	   toRead = 0;
	switch (gbRomType)
	{
	case 0x03:
	case 0x0f:
	case 0x10:
	case 0x13:
	case 0x1b:
	case 0x1e:
	case 0xff:
		read   = fread(gbRam, 1, (gbRamSizeMask + 1), file);
		toRead = (gbRamSizeMask + 1);
		break;
	case 0x06:
	case 0x22:
		read   = fread(&gbMemory[0xa000], 1, 256, file);
		toRead = 256;
		break;
	default:
		systemMessage(MSG_UNSUPPORTED_SNAPSHOT_FILE,
		              N_("Unsupported snapshot file %s"),
		              fileName);
		fclose(file);
		return false;
	}
	fclose(file);
	gbReset();
	return true;
}

static bool gbWriteBatteryToStream(gzFile gzfile)
{
	// the GB save code is ugly, so rather than convert it all to use gzFiles, just save it to a temp file...
#define TEMP_SAVE_FNAME ("tempvbawrite.sav")
	bool retVal = gbWriteBatteryFile(TEMP_SAVE_FNAME, true);

	// ...open the temp file and figure out its size...
	FILE *fileTemp = fopen(TEMP_SAVE_FNAME, "rb");
	if (fileTemp == NULL)
		return false;
	fseek(fileTemp, 0, SEEK_END);
	int len = (int) ftell(fileTemp);

	// ...copy over the temp file...
	char *temp = new char [len];
	fseek(fileTemp, 0, SEEK_SET);
	if (fread(temp, len, 1, fileTemp) != 1)
	{
		delete [] temp;
		fclose(fileTemp);
		return false;
	}
	fclose(fileTemp);
	utilGzWrite(gzfile, temp, len);
	delete [] temp;

	// ... and delete the temp file
	remove(TEMP_SAVE_FNAME);
#undef TEMP_SAVE_FNAME

	return retVal;
}

static bool gbReadBatteryFromStream(gzFile gzfile)
{
	// the GB save code is ugly, so rather than convert it all to use gzFiles, just copy it to temp RAM...
#define TEMP_SAVE_FNAME ("tempvbaread.sav")
	int	  pos	 = gztell(gzfile);
	int	  buflen = 1024;
	// ...make a temp file and write it there...
	FILE *fileTemp = fopen(TEMP_SAVE_FNAME, "wb");
	if (fileTemp == NULL)
		return false;
	int gzDeflated;
	char *temp	 = new char [buflen];
	while ((gzDeflated = utilGzRead(gzfile, temp, buflen)) != 0)
	{
		if (gzDeflated == -1 || fwrite(temp, gzDeflated, 1, fileTemp) != 1)
		{
			delete [] temp;
			fclose(fileTemp);
			gzseek(gzfile, pos, SEEK_SET); /// FIXME: leaves pos in gzfile before save instead of after it (everything that
			                               // calls this right now does a seek afterwards so it doesn't matter for now, but it's
			                               // still bad)
			return false;
		}
	}
	gzseek(gzfile, pos, SEEK_SET); /// FIXME: leaves pos in gzfile before save instead of after it (everything that calls this
	                               // right now does a seek afterwards so it doesn't matter for now, but it's still bad)
	fclose(fileTemp);
	delete [] temp;

	// ... load from the temp file...
	bool retVal = gbReadBatteryFile(TEMP_SAVE_FNAME);

	// ... and delete the temp file
	remove(TEMP_SAVE_FNAME);
#undef TEMP_SAVE_FNAME

	return retVal;
}

variable_desc gbSaveGameStruct[] = {
	{ &PC.W,					   sizeof(u16)	 },
	{ &SP.W,					   sizeof(u16)	 },
	{ &AF.W,					   sizeof(u16)	 },
	{ &BC.W,					   sizeof(u16)	 },
	{ &DE.W,					   sizeof(u16)	 },
	{ &HL.W,					   sizeof(u16)	 },
	{ &IFF,						   sizeof(u16)	 },
	{ &GBLCD_MODE_0_CLOCK_TICKS,   sizeof(int32) },
	{ &GBLCD_MODE_1_CLOCK_TICKS,   sizeof(int32) },
	{ &GBLCD_MODE_2_CLOCK_TICKS,   sizeof(int32) },
	{ &GBLCD_MODE_3_CLOCK_TICKS,   sizeof(int32) },
	{ &GBDIV_CLOCK_TICKS,		   sizeof(int32) },
	{ &GBLY_INCREMENT_CLOCK_TICKS, sizeof(int32) },
	{ &GBTIMER_MODE_0_CLOCK_TICKS, sizeof(int32) },
	{ &GBTIMER_MODE_1_CLOCK_TICKS, sizeof(int32) },
	{ &GBTIMER_MODE_2_CLOCK_TICKS, sizeof(int32) },
	{ &GBTIMER_MODE_3_CLOCK_TICKS, sizeof(int32) },
	{ &GBSERIAL_CLOCK_TICKS,	   sizeof(int32) },
	{ &GBSYNCHRONIZE_CLOCK_TICKS,  sizeof(int32) },
	{ &gbDivTicks,				   sizeof(int32) },
	{ &gbLcdMode,				   sizeof(int32) },
	{ &gbLcdTicks,				   sizeof(int32) },
	{ &gbLcdLYIncrementTicks,	   sizeof(int32) },
	{ &gbTimerTicks,			   sizeof(int32) },
	{ &gbTimerClockTicks,		   sizeof(int32) },
	{ &gbSerialTicks,			   sizeof(int32) },
	{ &gbSerialBits,			   sizeof(int32) },
	{ &gbInt48Signal,			   sizeof(int32) },
	{ &gbInterruptWait,			   sizeof(int32) },
	{ &gbSynchronizeTicks,		   sizeof(int32) },
	{ &gbTimerOn,				   sizeof(int32) },
	{ &gbTimerMode,				   sizeof(int32) },
	{ &gbSerialOn,				   sizeof(int32) },
	{ &gbWindowLine,			   sizeof(int32) },
	{ &gbCgbMode,				   sizeof(int32) },
	{ &gbVramBank,				   sizeof(int32) },
	{ &gbWramBank,				   sizeof(int32) },
	{ &gbHdmaSource,			   sizeof(int32) },
	{ &gbHdmaDestination,		   sizeof(int32) },
	{ &gbHdmaBytes,				   sizeof(int32) },
	{ &gbHdmaOn,				   sizeof(int32) },
	{ &gbSpeed,					   sizeof(int32) },
	{ &gbSgbMode,				   sizeof(int32) },
	{ &register_DIV,			   sizeof(u8)	 },
	{ &register_TIMA,			   sizeof(u8)	 },
	{ &register_TMA,			   sizeof(u8)	 },
	{ &register_TAC,			   sizeof(u8)	 },
	{ &register_IF,				   sizeof(u8)	 },
	{ &register_LCDC,			   sizeof(u8)	 },
	{ &register_STAT,			   sizeof(u8)	 },
	{ &register_SCY,			   sizeof(u8)	 },
	{ &register_SCX,			   sizeof(u8)	 },
	{ &register_LY,				   sizeof(u8)	 },
	{ &register_LYC,			   sizeof(u8)	 },
	{ &register_DMA,			   sizeof(u8)	 },
	{ &register_WY,				   sizeof(u8)	 },
	{ &register_WX,				   sizeof(u8)	 },
	{ &register_VBK,			   sizeof(u8)	 },
	{ &register_HDMA1,			   sizeof(u8)	 },
	{ &register_HDMA2,			   sizeof(u8)	 },
	{ &register_HDMA3,			   sizeof(u8)	 },
	{ &register_HDMA4,			   sizeof(u8)	 },
	{ &register_HDMA5,			   sizeof(u8)	 },
	{ &register_SVBK,			   sizeof(u8)	 },
	{ &register_IE,				   sizeof(u8)	 },
	{ &gbBgp[0],				   sizeof(u8)	 },
	{ &gbBgp[1],				   sizeof(u8)	 },
	{ &gbBgp[2],				   sizeof(u8)	 },
	{ &gbBgp[3],				   sizeof(u8)	 },
	{ &gbObp0[0],				   sizeof(u8)	 },
	{ &gbObp0[1],				   sizeof(u8)	 },
	{ &gbObp0[2],				   sizeof(u8)	 },
	{ &gbObp0[3],				   sizeof(u8)	 },
	{ &gbObp1[0],				   sizeof(u8)	 },
	{ &gbObp1[1],				   sizeof(u8)	 },
	{ &gbObp1[2],				   sizeof(u8)	 },
	{ &gbObp1[3],				   sizeof(u8)	 },
	{ &gbClockTicks,			   sizeof(int32) },
	{ &gbOldClockTicks,			   sizeof(int32) },
	{ &gbIntBreak,				   sizeof(int32) },
	{ &gbInterruptLaunched,		   sizeof(int32) },
	{ &gbInternalTimer,			   sizeof(int32) },
	{ &gbTimerModeChange,		   sizeof(int32) },
	{ &gbTimerOnChange,			   sizeof(int32) },
	{ &gbWhiteScreen,			   sizeof(int32) },
	{ &register_LCDCBusy,		   sizeof(int32) },
	{ &gbScreenTicks,			   sizeof(int32) },
	{ &gbLYChangeHappened,		   sizeof(int32) },
	{ &gbLCDChangeHappened,		   sizeof(int32) },
	{ &gbLine99Ticks,			   sizeof(int32) },
	{ &gbRegisterLYLCDCOffOn,	   sizeof(int32) },
	{ &inUseRegister_WX,		   sizeof(int32) },
	{ &stopCounter,				   sizeof(int32) },
	{ NULL,						   0								  }
};

static bool gbWriteSaveStateToStream(gzFile gzFile)
{
	utilWriteInt(gzFile, GBSAVE_GAME_VERSION);
	utilGzWrite(gzFile, &gbRom[0x134], 15);
	utilWriteInt(gzFile, useBios);
	utilWriteInt(gzFile, inBios);

	utilWriteData(gzFile, gbSaveGameStruct);

	utilGzWrite(gzFile, &IFF, 2);

	if (gbSgbMode)
	{
		gbSgbSaveGame(gzFile);
	}

	utilGzWrite(gzFile, &gbDataMBC1, sizeof(gbDataMBC1));
	utilGzWrite(gzFile, &gbDataMBC2, sizeof(gbDataMBC2));
	utilGzWrite(gzFile, &gbDataMBC3, sizeof(gbDataMBC3));
	utilGzWrite(gzFile, &gbDataMBC5, sizeof(gbDataMBC5));
	utilGzWrite(gzFile, &gbDataHuC1, sizeof(gbDataHuC1));
	utilGzWrite(gzFile, &gbDataHuC3, sizeof(gbDataHuC3));
	utilGzWrite(gzFile, &gbDataTAMA5, sizeof(gbDataTAMA5));
	if (gbTAMA5ram != NULL)
		utilGzWrite(gzFile, gbTAMA5ram, gbTAMA5ramSize);
	utilGzWrite(gzFile, &gbDataMMM01, sizeof(gbDataMMM01));

	// yes, this definitely needs to be saved, or loading paused games will show a black screen
	// this is also necessary to be consistent with what the GBA saving does
	utilGzWrite(gzFile, pix, 4 * 257 * 226);
	utilGzWrite(gzFile, gbPalette, 128 * sizeof(u16));
	utilGzWrite(gzFile, &gbMemory[0x8000], 0x8000);

	if (gbRamSize && gbRam)
	{
		utilWriteInt(gzFile, gbRamSize);
		utilGzWrite(gzFile, gbRam, gbRamSize);
	}

#define WRITE_MEM_MAP(to, from, rel) \
	utilWriteInt(gzFile, gbMemoryMap[to] - rel);
	{
		WRITE_MEM_MAP(0x00, 0x0000, gbRom);
		WRITE_MEM_MAP(0x01, 0x1000, gbRom);
		WRITE_MEM_MAP(0x02, 0x2000, gbRom);
		WRITE_MEM_MAP(0x03, 0x3000, gbRom);
		WRITE_MEM_MAP(0x04, 0x4000, gbRom);
		WRITE_MEM_MAP(0x05, 0x5000, gbRom);
		WRITE_MEM_MAP(0x06, 0x6000, gbRom);
		WRITE_MEM_MAP(0x07, 0x7000, gbRom);
		WRITE_MEM_MAP(0x08, 0x8000, gbMemory);
		WRITE_MEM_MAP(0x09, 0x9000, gbMemory);
		WRITE_MEM_MAP(0x0a, 0xa000, gbMemory);
		WRITE_MEM_MAP(0x0b, 0xb000, gbMemory);
		WRITE_MEM_MAP(0x0c, 0xc000, gbMemory);
		WRITE_MEM_MAP(0x0d, 0xd000, gbMemory);
		WRITE_MEM_MAP(0x0e, 0xe000, gbMemory);
		WRITE_MEM_MAP(0x0f, 0xf000, gbMemory);
	}
#undef READ_MEM_MAP

	if (gbCgbMode)
	{
		utilGzWrite(gzFile, gbVram, 0x4000);
		utilGzWrite(gzFile, gbWram, 0x8000);
	}

	gbSoundSaveGame(gzFile);
	gbCheatsSaveGame(gzFile);

	utilWriteInt(gzFile, gbLcdModeDelayed);
	utilWriteInt(gzFile, gbLcdTicksDelayed);
	utilWriteInt(gzFile, gbLcdLYIncrementTicksDelayed);
	utilWriteInt(gzFile, gbSpritesTicks[299]);
	utilWriteInt(gzFile, gbTimerModeChange);
	utilWriteInt(gzFile, gbTimerOnChange);
	utilWriteInt(gzFile, gbHardware);
	utilWriteInt(gzFile, gbBlackScreen);
#if 0
	utilWriteInt(gzFile, oldRegister_WY);	// no longer
#else
	utilWriteInt(gzFile, inUseRegister_WY);
#endif
	utilWriteInt(gzFile, gbWindowLine);
	utilWriteInt(gzFile, inUseRegister_WY);
	utilWriteInt(gzFile, gbScreenOn);

	// new to re-recording version:
	{
		utilGzWrite(gzFile, &sensorX, sizeof(sensorX));
		utilGzWrite(gzFile, &sensorY, sizeof(sensorY));
		utilGzWrite(gzFile, gbJoymask, 4 * sizeof(*gbJoymask)); // this has to be saved or old input will incorrectly get
		                                                        // carried
		                                                        // back on loading a snapshot!

		bool8 movieActive = VBAMovieIsActive();
		utilGzWrite(gzFile, &movieActive, sizeof(movieActive));
		if (movieActive)
		{
			uint8 *movie_freeze_buf	 = NULL;
			uint32 movie_freeze_size = 0;

			VBAMovieFreeze(&movie_freeze_buf, &movie_freeze_size);
			if (movie_freeze_buf)
			{
				utilGzWrite(gzFile, &movie_freeze_size, sizeof(movie_freeze_size));
				utilGzWrite(gzFile, movie_freeze_buf, movie_freeze_size);
				delete [] movie_freeze_buf;
			}
			else
			{
				systemMessage(0, N_("Failed to save movie snapshot."));
				return false;
			}
		}
		utilGzWrite(gzFile, &systemCounters.frameCount, sizeof(systemCounters.frameCount));
	}

	// new to rerecording 19.4 wip (svn r22+):
	{
		utilGzWrite(gzFile, &systemCounters.lagCount, sizeof(systemCounters.lagCount));
		utilGzWrite(gzFile, &systemCounters.lagged, sizeof(systemCounters.lagged));
		utilGzWrite(gzFile, &systemCounters.laggedLast, sizeof(systemCounters.laggedLast));
	}

	return true;
}

bool gbWriteMemSaveState(char *memory, int available)
{
	gzFile gzFile = utilMemGzOpen(memory, available, "w");

	if (gzFile == NULL)
	{
		return false;
	}

	bool res = gbWriteSaveStateToStream(gzFile);

	long pos = utilGzTell(gzFile) + 8;

	if (pos >= (available))
		res = false;

	utilGzClose(gzFile);

	return res;
}

bool gbWriteSaveState(const char *name)
{
	gzFile gzFile = utilGzOpen(name, "wb");

	if (gzFile == NULL)
		return false;

	bool res = gbWriteSaveStateToStream(gzFile);

	utilGzClose(gzFile);
	return res;
}

static bool gbReadSaveStateFromStream(gzFile gzFile)
{
	char tempBackupName[128];
	if (tempSaveSafe)
	{
		sprintf(tempBackupName, "gbtempsave%d.sav", tempSaveID++);
		gbWriteSaveState(tempBackupName);
	}

	int version = utilReadInt(gzFile);
	if (version > GBSAVE_GAME_VERSION || version < GBSAVE_GAME_VERSION_13)
	{
		systemMessage(MSG_UNSUPPORTED_VB_SGM,
		              N_("Unsupported VisualBoy save game version %d"), version);
		return false;
	}

	u8 romname[20];
	utilGzRead(gzFile, romname, 15);

	if (memcmp(&gbRom[0x134], romname, 15) != 0)
	{
		systemMessage(MSG_CANNOT_LOAD_SGM_FOR,
		              N_("Cannot load save game for %s. Playing %s"),
		              romname, &gbRom[0x134]);
		return false;
	}

	bool8 ub = false;
	bool8 ib = false;
	if (version >= GBSAVE_GAME_VERSION_11)
	{
		ub = utilReadInt(gzFile) ? true : false;
		ib = utilReadInt(gzFile) ? true : false;

		if ((ub != useBios) && (ib))
		{
			if (useBios)
				systemMessage(MSG_SAVE_GAME_NOT_USING_BIOS,
				              N_("Save game is not using the BIOS files"));
			else
				systemMessage(MSG_SAVE_GAME_USING_BIOS,
				              N_("Save game is using the BIOS file"));
			return false;
		}
	}

	// FIXME: ?????????
//	gbReset();

	inBios = ib;

	utilReadData(gzFile, gbSaveGameStruct);

	// Correct crash when loading color gameboy save in regular gameboy type.
	if (gbCgbMode)
	{
		if (!gbVram)
			gbVram = (u8 *)RAM_MALLOC(0x4000);
		if (!gbWram)
			gbWram = (u8 *)RAM_MALLOC(0x8000);
		memset(gbPalette, 0, 2 * 128);
	}
	else
	{
		free(gbVram);
		gbVram = NULL;
		free(gbWram);
		gbWram = NULL;
	}

	if (version >= GBSAVE_GAME_VERSION_7)
	{
		utilGzRead(gzFile, &IFF, 2);
	}

	if (gbSgbMode)
	{
		gbSgbReadGame(gzFile, version);
	}
	else
	{
		gbSgbMask = 0; // loading a game at the wrong time causes no display
	}
	if (version < GBSAVE_GAME_VERSION_11)
		utilGzRead(gzFile, &gbDataMBC1, sizeof(gbDataMBC1) - sizeof(int32));
	else
		utilGzRead(gzFile, &gbDataMBC1, sizeof(gbDataMBC1));
	utilGzRead(gzFile, &gbDataMBC2, sizeof(gbDataMBC2));
	if (version < GBSAVE_GAME_VERSION_4)
		// prior to version 4, there was no adjustment for the time the game
		// was last played, so we have less to read. This needs update if the
		// structure changes again.
		utilGzRead(gzFile, &gbDataMBC3, sizeof(gbDataMBC3) - sizeof(u32));
	else
		utilGzRead(gzFile, &gbDataMBC3, sizeof(gbDataMBC3));
	utilGzRead(gzFile, &gbDataMBC5, sizeof(gbDataMBC5));
	utilGzRead(gzFile, &gbDataHuC1, sizeof(gbDataHuC1));
	utilGzRead(gzFile, &gbDataHuC3, sizeof(gbDataHuC3));

	if (version >= GBSAVE_GAME_VERSION_11)
	{
		utilGzRead(gzFile, &gbDataTAMA5, sizeof(gbDataTAMA5));
		if (gbTAMA5ram != NULL)
		{
			if (skipSaveGameBattery)
			{
				utilGzSeek(gzFile, gbTAMA5ramSize, SEEK_CUR);
			}
			else
			{
				utilGzRead(gzFile, gbTAMA5ram, gbTAMA5ramSize);
			}
		}
		utilGzRead(gzFile, &gbDataMMM01, sizeof(gbDataMMM01));
	}

	if (version < GBSAVE_GAME_VERSION_5)
	{
		utilGzRead(gzFile, pix, 256 * 224 * sizeof(u16));
	}
	else if (version >= GBSAVE_GAME_VERSION_12)
	{
		utilGzRead(gzFile, pix, 4 * 257 * 226);
	}
	else
	{
		memset(pix, 0, 257 * 226 * sizeof(u32));
//		if(version < GBSAVE_GAME_VERSION_5)
//			utilGzRead(gzFile, pix, 256*224*sizeof(u16));
	}

	if (version < GBSAVE_GAME_VERSION_6)
	{
		utilGzRead(gzFile, gbPalette, 64 * sizeof(u16));
	}
	else
		utilGzRead(gzFile, gbPalette, 128 * sizeof(u16));

	if (version < GBSAVE_GAME_VERSION_10)
	{
		if (!gbCgbMode && !gbSgbMode)
		{
			for (int i = 0; i < 8; i++)
				gbPalette[i] = systemGbPalette[gbPaletteOption * 8 + i];
		}
	}

	utilGzRead(gzFile, &gbMemory[0x8000], 0x8000);

	if (gbRamSize && gbRam)
	{
		if (version < GBSAVE_GAME_VERSION_11)
			if (skipSaveGameBattery)
			{
				utilGzSeek(gzFile, gbRamSize, SEEK_CUR); //skip
			}
			else
			{
				utilGzRead(gzFile, gbRam, gbRamSize); //read
			}
		else
		{
			int ramSize = utilReadInt(gzFile);
			if (skipSaveGameBattery)
			{
				utilGzSeek(gzFile, (gbRamSize > ramSize) ? ramSize : gbRamSize, SEEK_CUR); //skip
			}
			else
			{
				utilGzRead(gzFile, gbRam, (gbRamSize > ramSize) ? ramSize : gbRamSize); //read
			}
			if (ramSize > gbRamSize)
				utilGzSeek(gzFile, ramSize - gbRamSize, SEEK_CUR);
		}
	}

	memset(gbSCYLine, register_SCY, sizeof(gbSCYLine));
	memset(gbSCXLine, register_SCX, sizeof(gbSCXLine));
	memset(gbBgpLine, (gbBgp[0] | (gbBgp[1] << 2) | (gbBgp[2] << 4) |
	                   (gbBgp[3] << 6)), sizeof(gbBgpLine));
	memset(gbObp0Line, (gbObp0[0] | (gbObp0[1] << 2) | (gbObp0[2] << 4) |
	                    (gbObp0[3] << 6)), sizeof(gbObp0Line));
	memset(gbObp1Line, (gbObp1[0] | (gbObp1[1] << 2) | (gbObp1[2] << 4) |
	                    (gbObp1[3] << 6)), sizeof(gbObp1Line));
	memset(gbSpritesTicks, 0x0, sizeof(gbSpritesTicks));

	if (inBios)
	{
		gbMemoryMap[0x00] = &gbMemory[0x0000];
		memcpy((u8 *)(gbMemory), (u8 *)(gbRom), 0x1000);
		memcpy((u8 *)(gbMemory), (u8 *)(bios), 0x100);
	}
	else
	{
#define READ_MEM_MAP(to, from, rel) \
	gbMemoryMap[to] = rel + utilReadInt(gzFile);

		READ_MEM_MAP(0x00, 0x0000, gbRom);
		READ_MEM_MAP(0x01, 0x1000, gbRom);
		READ_MEM_MAP(0x02, 0x2000, gbRom);
		READ_MEM_MAP(0x03, 0x3000, gbRom);
		READ_MEM_MAP(0x04, 0x4000, gbRom);
		READ_MEM_MAP(0x05, 0x5000, gbRom);
		READ_MEM_MAP(0x06, 0x6000, gbRom);
		READ_MEM_MAP(0x07, 0x7000, gbRom);
		READ_MEM_MAP(0x08, 0x8000, gbMemory);
		READ_MEM_MAP(0x09, 0x9000, gbMemory);
		READ_MEM_MAP(0x0a, 0xa000, gbMemory);
		READ_MEM_MAP(0x0b, 0xb000, gbMemory);
		READ_MEM_MAP(0x0c, 0xc000, gbMemory);
		READ_MEM_MAP(0x0d, 0xd000, gbMemory);
		READ_MEM_MAP(0x0e, 0xe000, gbMemory);
		READ_MEM_MAP(0x0f, 0xf000, gbMemory);

#undef READ_MEM_MAP
	}

	switch (gbRomType)
	{
	case 0x00:
	case 0x01:
	case 0x02:
	case 0x03:
		// MBC 1
		memoryUpdateMapMBC1();
		break;
	case 0x05:
	case 0x06:
		// MBC2
		memoryUpdateMapMBC2();
		break;
	case 0x0b:
	case 0x0c:
	case 0x0d:
		// MMM01
		memoryUpdateMapMMM01();
		break;
	case 0x0f:
	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13:
		// MBC 3
		memoryUpdateMapMBC3();
		break;
	case 0x19:
	case 0x1a:
	case 0x1b:
		// MBC5
		memoryUpdateMapMBC5();
		break;
	case 0x1c:
	case 0x1d:
	case 0x1e:
		// MBC 5 Rumble
		memoryUpdateMapMBC5();
		break;
	case 0x22:
		// MBC 7
		memoryUpdateMapMBC7();
		break;
	case 0x56:
		// GS3
		memoryUpdateMapGS3();
		break;
	case 0xfd:
		// TAMA5
		memoryUpdateMapTAMA5();
		break;
	case 0xfe:
		// HuC3
		memoryUpdateMapHuC3();
		break;
	case 0xff:
		// HuC1
		memoryUpdateMapHuC1();
		break;
	}

	if (gbCgbMode)
	{
		utilGzRead(gzFile, gbVram, 0x4000);
		utilGzRead(gzFile, gbWram, 0x8000);

		int value = register_SVBK;
		if (value == 0)
			value = 1;

		gbMemoryMap[0x08] = &gbVram[register_VBK * 0x2000];
		gbMemoryMap[0x09] = &gbVram[register_VBK * 0x2000 + 0x1000];
		gbMemoryMap[0x0d] = &gbWram[value * 0x1000];
	}

	gbSoundReadGame(version, gzFile);

	if (gbCgbMode && gbSgbMode)
	{
		gbSgbMode = 0;
	}

#if 0
	if (gbBorderOn && !gbSgbMask)
	{
		gbSgbRenderBorder();
	}

	systemRefreshScreen();
#endif

	if (version > GBSAVE_GAME_VERSION_1)
	{
		if (skipSaveGameCheats)
		{
			gbCheatsReadGameSkip(gzFile, version);
		}
		else
		{
			gbCheatsReadGame(gzFile, version);
		}
	}

	if (version < GBSAVE_GAME_VERSION_11)
	{
		gbWriteMemory(0xff00, 0);
		gbMemory[0xff04]  = register_DIV;
		gbMemory[0xff05]  = register_TIMA;
		gbMemory[0xff06]  = register_TMA;
		gbMemory[0xff07]  = register_TAC;
		gbMemory[0xff40]  = register_LCDC;
		gbMemory[0xff42]  = register_SCY;
		gbMemory[0xff43]  = register_SCX;
		gbMemory[0xff44]  = register_LY;
		gbMemory[0xff45]  = register_LYC;
		gbMemory[0xff46]  = register_DMA;
		gbMemory[0xff4a]  = register_WY;
		gbMemory[0xff4b]  = register_WX;
		gbMemory[0xff4f]  = register_VBK;
		gbMemory[0xff51]  = register_HDMA1;
		gbMemory[0xff52]  = register_HDMA2;
		gbMemory[0xff53]  = register_HDMA3;
		gbMemory[0xff54]  = register_HDMA4;
		gbMemory[0xff55]  = register_HDMA5;
		gbMemory[0xff70]  = register_SVBK;
		gbMemory[0xffff]  = register_IE;
		GBDIV_CLOCK_TICKS = 64;

		if (gbSpeed)
			gbDivTicks /= 2;

		if ((gbLcdMode == 0) && (register_STAT & 8))
			gbInt48Signal |= 1;
		if ((gbLcdMode == 1) && (register_STAT & 0x10))
			gbInt48Signal |= 2;
		if ((gbLcdMode == 2) && (register_STAT & 0x20))
			gbInt48Signal |= 4;
		if ((register_LY == register_LYC) && (register_STAT & 0x40))
			gbInt48Signal |= 8;

		gbLcdLYIncrementTicks = GBLY_INCREMENT_CLOCK_TICKS;

		if (gbLcdMode == 2)
			gbLcdLYIncrementTicks -= GBLCD_MODE_2_CLOCK_TICKS - gbLcdTicks;
		else if (gbLcdMode == 3)
			gbLcdLYIncrementTicks -= GBLCD_MODE_2_CLOCK_TICKS + GBLCD_MODE_3_CLOCK_TICKS - gbLcdTicks;
		else if (gbLcdMode == 0)
			gbLcdLYIncrementTicks = gbLcdTicks;
		else if (gbLcdMode == 1)
		{
			gbLcdLYIncrementTicks = gbLcdTicks % GBLY_INCREMENT_CLOCK_TICKS;
			if (register_LY == 0x99)
				gbLcdLYIncrementTicks = gbLine99Ticks;
			else if (register_LY == 0)
				gbLcdLYIncrementTicks += GBLY_INCREMENT_CLOCK_TICKS;
		}

		gbLcdModeDelayed  = gbLcdMode;
		gbLcdTicksDelayed = gbLcdTicks--;
		gbLcdLYIncrementTicksDelayed = gbLcdLYIncrementTicks--;
		gbInterruptWait = 0;
		memset(gbSpritesTicks, 0, sizeof(gbSpritesTicks));
	}
	else
	{
		gbLcdModeDelayed  = utilReadInt(gzFile);
		gbLcdTicksDelayed = utilReadInt(gzFile);
		gbLcdLYIncrementTicksDelayed = utilReadInt(gzFile);
		gbSpritesTicks[299] = utilReadInt(gzFile) & 0xff;
		gbTimerModeChange	= (utilReadInt(gzFile) ? true : false);
		gbTimerOnChange		= (utilReadInt(gzFile) ? true : false);
		gbHardware			= utilReadInt(gzFile);
		gbBlackScreen		= (utilReadInt(gzFile) ? true : false);
#if 0
		oldRegister_WY		= utilReadInt(gzFile);	// no longer
#else
		inUseRegister_WY	= utilReadInt(gzFile);
#endif
		gbWindowLine		= utilReadInt(gzFile);
		inUseRegister_WY	= utilReadInt(gzFile);
		gbScreenOn			= (utilReadInt(gzFile) ? true : false);
	}

	// FIXME: ?????????
//	if (gbSpeed)
//		gbLine99Ticks *= 2;

	systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

	if (version >= GBSAVE_GAME_VERSION_11) // new to re-recording version:
	{
		utilGzRead(gzFile, &sensorX, sizeof(sensorX));
		utilGzRead(gzFile, &sensorY, sizeof(sensorY));
		utilGzRead(gzFile, gbJoymask, 4 * sizeof(*gbJoymask)); // this has to be saved or old input will incorrectly get carried
		                                                       // back on loading a snapshot!

		bool8 movieSnapshot;
		utilGzRead(gzFile, &movieSnapshot, sizeof(movieSnapshot));
		if (VBAMovieIsActive() && !movieSnapshot)
		{
			systemMessage(0, N_("Can't load a non-movie snapshot while a movie is active."));
			goto failedLoadGB;
		}

		if (movieSnapshot) // even if a movie isn't active we still want to parse through this
						   // because we have already got stuff added in this save format
		{
			uint32 movieInputDataSize = 0;
			utilGzRead(gzFile, &movieInputDataSize, sizeof(movieInputDataSize));
			uint8 *local_movie_data = new uint8 [movieInputDataSize];
			int	   readBytes		= utilGzRead(gzFile, local_movie_data, movieInputDataSize);
			if (readBytes != movieInputDataSize)
			{
				systemMessage(0, N_("Corrupt movie snapshot."));
				delete [] local_movie_data;
				goto failedLoadGB;
			}
			int code = VBAMovieUnfreeze(local_movie_data, movieInputDataSize);
			delete [] local_movie_data;
			if (code != MOVIE_SUCCESS && VBAMovieIsActive())
			{
				char errStr [1024];
				strcpy(errStr, "Failed to load movie snapshot");
				switch (code)
				{
				case MOVIE_NOT_FROM_THIS_MOVIE:
					strcat(errStr, ";\nSnapshot not from this movie"); break;
				case MOVIE_NOT_FROM_A_MOVIE:
					strcat(errStr, ";\nNot a movie snapshot"); break;                    // shouldn't get here...
				case MOVIE_SNAPSHOT_INCONSISTENT:
					strcat(errStr, ";\nSnapshot inconsistent with movie"); break;
				case MOVIE_WRONG_FORMAT:
					strcat(errStr, ";\nWrong format"); break;
				}
				strcat(errStr, ".");
				systemMessage(0, N_(errStr));
				goto failedLoadGB;
			}
		}
		utilGzRead(gzFile, &systemCounters.frameCount, sizeof(systemCounters.frameCount));
	}

	if (version >= GBSAVE_GAME_VERSION_13)   // new to rerecording 19.4 wip (svn r22+):
	{
		utilGzRead(gzFile, &systemCounters.lagCount, sizeof(systemCounters.lagCount));
		utilGzRead(gzFile, &systemCounters.lagged, sizeof(systemCounters.lagged));
		utilGzRead(gzFile, &systemCounters.laggedLast, sizeof(systemCounters.laggedLast));
	}

	for (int i = 0; i < 4; ++i)
		systemSetJoypad(i, gbJoymask[i] & 0xFFFF);

	VBAUpdateButtonPressDisplay();
	VBAUpdateFrameCountDisplay();
	systemRefreshScreen();

	if (tempSaveSafe)
	{
		remove(tempBackupName);
		tempSaveAttempts = 0;
	}
	return true;

failedLoadGB:
	if (tempSaveSafe)
	{
		tempSaveAttempts++;
		if (tempSaveAttempts < 3) // fail no more than 2 times in a row
			gbReadSaveState(tempBackupName);
		remove(tempBackupName);
	}
	return false;
}

bool gbReadMemSaveState(char *memory, int available)
{
	gzFile gzFile = utilMemGzOpen(memory, available, "r");

	tempSaveSafe = false;
	bool res = gbReadSaveStateFromStream(gzFile);
	tempSaveSafe = true;

	utilGzClose(gzFile);

	return res;
}

bool gbReadSaveState(const char *name)
{
	gzFile gzFile = utilGzOpen(name, "rb");

	if (gzFile == NULL)
	{
		return false;
	}

	bool res = gbReadSaveStateFromStream(gzFile);

	utilGzClose(gzFile);

	return res;
}

bool gbWritePNGFile(const char *fileName)
{
	if (gbBorderOn)
		return utilWritePNGFile(fileName, 256, 224, pix);
	return utilWritePNGFile(fileName, 160, 144, pix);
}

bool gbWriteBMPFile(const char *fileName)
{
	if (gbBorderOn)
		return utilWriteBMPFile(fileName, 256, 224, pix);
	return utilWriteBMPFile(fileName, 160, 144, pix);
}

void gbCleanUp()
{
	PIX_FREE(pix);
	pix = NULL;

	free(bios);
	bios = NULL;

	free(gbRam);
	gbRam = NULL;

	free(gbRom);
	gbRom = NULL;

	free(gbMemory);
	gbMemory = NULL;

	free(gbVram);
	gbVram = NULL;

	free(gbWram);
	gbWram = NULL;

	free(gbTAMA5ram);
	gbTAMA5ram = NULL;

	gbSgbShutdown();

	free(gbLineBuffer);
	gbLineBuffer = NULL;

	memset(gbJoymask, 0, sizeof(gbJoymask));

	stopCounter = 0;

	systemCleanUp();
	systemRefreshScreen();
}

bool gbLoadRom(const char *szFile)
{
	int size = 0;

	if (gbRom != NULL)
	{
		gbCleanUp();
	}

	systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

	gbRom = utilLoad(szFile,
	                 utilIsGBImage,
	                 NULL,
	                 size);
	if (!gbRom)
		return false;

	gbRomSize = size;

	gbBatteryError = false;

	return gbUpdateSizes();
}

bool gbUpdateSizes()
{
	if (gbRom[0x148] > 8)
	{
		systemMessage(MSG_UNSUPPORTED_ROM_SIZE,
		              N_("Unsupported rom size %02x"), gbRom[0x148]);
		return false;
	}

	if (gbRomSize < gbRomSizes[gbRom[0x148]])
	{
		u8 *gbRomNew = (u8 *)realloc(gbRom, gbRomSizes[gbRom[0x148]]);
		if (!gbRomNew) { assert(false); return false; };
		gbRom = gbRomNew;
		for (int i = gbRomSize; i < gbRomSizes[gbRom[0x148]]; i++)
			gbRom[i] = 0x00;  // Not sure if it's 0x00, 0xff or random data...
	}
	// (it's in the case a cart is 'lying' on its size.
	else if ((gbRomSize > gbRomSizes[gbRom[0x148]]) && (genericflashcardEnable))
	{
		gbRomSize	 = gbRomSize >> 16;
		gbRom[0x148] = 0;
		if (gbRomSize)
		{
			while (!((gbRomSize & 1) || (gbRom[0x148] == 7)))
			{
				gbRom[0x148]++;
				gbRomSize >>= 1;
			}
			gbRom[0x148]++;
		}
		u8 *gbRomNew = (u8 *)realloc(gbRom, gbRomSizes[gbRom[0x148]]);
		if (!gbRomNew) { assert(false); return false; };
		gbRom = gbRomNew;
	}
	gbRomSize	  = gbRomSizes[gbRom[0x148]];
	gbRomSizeMask = gbRomSizesMasks[gbRom[0x148]];

	// The 'genericflashcard' option allows some PD to work.
	// However, the setting is dangerous (if you let in enabled
	// and play a normal game, it might just break everything).
	// That's why it is not saved in the emulator options.
	// Also I added some checks in VBA to make sure your saves will not be
	// overwritten if you wrongly enable this option for a game
	// you already played (and vice-versa, ie. if you forgot to
	// enable the option for a game you played with it enabled, like Shawu Story).
	u8 ramsize = genericflashcardEnable ? 5 : gbRom[0x149];
	gbRom[0x149] = ramsize;

	if ((gbRom[2] == 0x6D) && (gbRom[5] == 0x47) && (gbRom[6] == 0x65) && (gbRom[7] == 0x6E) &&
	    (gbRom[8] == 0x69) && (gbRom[9] == 0x65) && (gbRom[0xA] == 0x28) && (gbRom[0xB] == 0x54))
	{
		gbCheatingDevice = 1; // GameGenie
		for (int i = 0; i < 0x20; i++) // Cleans GG hardware registers
			gbRom[0x4000 + i] = 0;
	}
	else if (((gbRom[0x104] == 0x44) && (gbRom[0x156] == 0xEA) && (gbRom[0x158] == 0x7F) &&
	          (gbRom[0x159] == 0xEA) && (gbRom[0x15B] == 0x7F)) || ((gbRom[0x165] == 0x3E) &&
	                                                                (gbRom[0x166] == 0xD9) && (gbRom[0x16D] == 0xE1) && (gbRom[0x16E] == 0x7F)))
		gbCheatingDevice = 2;  // GameShark
	else gbCheatingDevice = 0;

	if (ramsize > 5)
	{
		systemMessage(MSG_UNSUPPORTED_RAM_SIZE,
		              N_("Unsupported ram size %02x"), gbRom[0x149]);
		return false;
	}

	gbRamSize	  = gbRamSizes[ramsize];
	gbRamSizeMask = gbRamSizesMasks[ramsize];

	gbRomType = gbRom[0x147];
	if (genericflashcardEnable)
	{
		/*if (gbRomType<2)
		   gbRomType =3;
		   else if ((gbRomType == 0xc) || (gbRomType == 0xf) || (gbRomType == 0x12) ||
		         (gbRomType == 0x16) || (gbRomType == 0x1a) || (gbRomType == 0x1d))
		   gbRomType++;
		   else if ((gbRomType == 0xb) || (gbRomType == 0x11) || (gbRomType == 0x15) ||
		         (gbRomType == 0x19) || (gbRomType == 0x1c))
		   gbRomType+=2;
		   else if ((gbRomType == 0x5) || (gbRomType == 0x6))
		   gbRomType = 0x1a;*/
		gbRomType = 0x1b;
	}
	else if (gbCheatingDevice == 1)
		gbRomType = 0x55;
	else if (gbCheatingDevice == 2)
		gbRomType = 0x56;

	gbRom[0x147] = gbRomType;

	mapperReadRAM = NULL;

	switch (gbRomType)
	{
	case 0x00:
	case 0x01:
	case 0x02:
	case 0x03:
	case 0x08:
	case 0x09:
		// MBC 1
		mapper		  = mapperMBC1ROM;
		mapperRAM	  = mapperMBC1RAM;
		mapperReadRAM = mapperMBC1ReadRAM;
		break;
	case 0x05:
	case 0x06:
		// MBC2
		mapper		  = mapperMBC2ROM;
		mapperRAM	  = mapperMBC2RAM;
		gbRamSize	  = 0x200;
		gbRamSizeMask = 0x1ff;
		break;
	case 0x0b:
	case 0x0c:
	case 0x0d:
		// MMM01
		mapper	  = mapperMMM01ROM;
		mapperRAM = mapperMMM01RAM;
		break;
	case 0x0f:
	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13:
	case 0xfc:
		// MBC 3
		mapper		  = mapperMBC3ROM;
		mapperRAM	  = mapperMBC3RAM;
		mapperReadRAM = mapperMBC3ReadRAM;
		break;
	case 0x19:
	case 0x1a:
	case 0x1b:
		// MBC5
		mapper		  = mapperMBC5ROM;
		mapperRAM	  = mapperMBC5RAM;
		mapperReadRAM = mapperMBC5ReadRAM;
		break;
	case 0x1c:
	case 0x1d:
	case 0x1e:
		// MBC 5 Rumble
		mapper		  = mapperMBC5ROM;
		mapperRAM	  = mapperMBC5RAM;
		mapperReadRAM = mapperMBC5ReadRAM;
		break;
	case 0x22:
		// MBC 7
		mapper		  = mapperMBC7ROM;
		mapperRAM	  = mapperMBC7RAM;
		mapperReadRAM = mapperMBC7ReadRAM;
		gbRamSize	  = 0x200;
		gbRamSizeMask = 0x1ff;
		break;
	// GG (GameGenie)
	case 0x55:
		mapper = mapperGGROM;
		break;
	case 0x56:
		// GS (GameShark)
		mapper = mapperGS3ROM;
		break;
	case 0xfd:
		// TAMA5
		if (gbRam != NULL)
		{
			free(gbRam);
			gbRam = NULL;
		}

		ramsize		  = 3;
		gbRamSize	  = gbRamSizes[3];
		gbRamSizeMask = gbRamSizesMasks[3];
		gbRamFill	  = 0x0;

		gbTAMA5ramSize = 0x100;

		if (gbTAMA5ram == NULL)
			gbTAMA5ram = (u8 *)malloc(gbTAMA5ramSize);
		memset(gbTAMA5ram, 0x0, gbTAMA5ramSize);

		mapperRAM		  = mapperTAMA5RAM;
		mapperReadRAM	  = mapperTAMA5ReadRAM;
		mapperUpdateClock = memoryUpdateTAMA5Clock;
		break;
	case 0xfe:
		// HuC3
		mapper		  = mapperHuC3ROM;
		mapperRAM	  = mapperHuC3RAM;
		mapperReadRAM = mapperHuC3ReadRAM;
		break;
	case 0xff:
		// HuC1
		mapper	  = mapperHuC1ROM;
		mapperRAM = mapperHuC1RAM;
		break;
	default:
		systemMessage(MSG_UNKNOWN_CARTRIDGE_TYPE,
		              N_("Unknown cartridge type %02x"), gbRomType);
		return false;
	}

	if (gbRamSize)
	{
		if (gbRam)
			free(gbRam);
		gbRam = (u8 *)malloc(gbRamSize);
		memset(gbRam, gbRamFill, gbRamSize);
	}

	switch (gbRomType)
	{
	case 0x03:
	case 0x06:
	case 0x0f:
	case 0x10:
	case 0x13:
	case 0x1b:
	case 0x1d:
	case 0x1e:
	case 0x22:
	case 0xfd:
	case 0xff:
		gbBattery = 1;
		break;
	}

	gbInit();
	gbReset();

	return true;
}

int gbGetNextEvent(int _gbClockTicks)
{
	if (register_LCDC & 0x80)
	{
		if (gbLcdTicks < _gbClockTicks)
			_gbClockTicks = gbLcdTicks;

		if (gbLcdTicksDelayed < _gbClockTicks)
			_gbClockTicks = gbLcdTicksDelayed;

		if (gbLcdLYIncrementTicksDelayed < _gbClockTicks)
			_gbClockTicks = gbLcdLYIncrementTicksDelayed;
	}

	if (gbLcdLYIncrementTicks < _gbClockTicks)
		_gbClockTicks = gbLcdLYIncrementTicks;

	if (gbSerialOn && (gbSerialTicks < _gbClockTicks))
		_gbClockTicks = gbSerialTicks;

	if (gbTimerOn && (((gbInternalTimer) & gbTimerMask[gbTimerMode]) + 1 < _gbClockTicks))
		_gbClockTicks = ((gbInternalTimer) & gbTimerMask[gbTimerMode]) + 1;

	//if(soundTicks && (soundTicks < _gbClockTicks))
	//  _gbClockTicks = soundTicks;

	if ((_gbClockTicks <= 0) || (gbInterruptWait))
		_gbClockTicks = 1;

	return _gbClockTicks;
}

static void gbDrawPixLine()
{
	switch (systemColorDepth)
	{
	case 16:
	{
		u16 *dest = (u16 *)pix +
		            (gbBorderLineSkip + 2) * (register_LY + gbBorderRowSkip + 1)
		            + gbBorderColumnSkip;
		for (int x = 0; x < 160; )
		{
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];

			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];

			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];

			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
			*dest++ = systemColorMap16[gbLineMix[x++]];
		}
		if (gbBorderOn)
			dest += gbBorderColumnSkip;
		*dest++ = 0; // for filters that read one pixel more
		break;
	}
	case 24:
	{
		u8 *dest = (u8 *)pix +
		           3 * (gbBorderLineSkip * (register_LY + gbBorderRowSkip) +
		                gbBorderColumnSkip);
		for (int x = 0; x < 160; )
		{
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;

			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;

			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;

			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
			*((u32 *)dest) = systemColorMap32[gbLineMix[x++]];
			dest += 3;
		}
		break;
	}
	case 32:
	{
		u32 *dest = (u32 *)pix +
		            (gbBorderLineSkip + 1) * (register_LY + gbBorderRowSkip + 1)
		            + gbBorderColumnSkip;
		for (int x = 0; x < 160; )
		{
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];

			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];

			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];

			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
			*dest++ = systemColorMap32[gbLineMix[x++]];
		}
		break;
	}
	}
}

static inline void gbGetUserInput()
{
	// update joystick information
	systemReadJoypads();

	bool sensor = (gbRom[0x147] == 0x22);
	if (gbSgbMode && gbSgbMultiplayer)
	{
		if (gbSgbFourPlayers)
		{
			gbJoymask[0] = systemGetJoypad(0, sensor);
			gbJoymask[1] = systemGetJoypad(1, false);
			gbJoymask[2] = systemGetJoypad(2, false);
			gbJoymask[3] = systemGetJoypad(3, false);
		}
		else
		{
			gbJoymask[0] = systemGetJoypad(0, sensor);
			gbJoymask[1] = systemGetJoypad(1, false);
		}
	}
	else
	{
		gbJoymask[0] = systemGetJoypad(0, sensor);
	}

	if (gbJoymask[0] & 0xFF)
	{
		gbMemory[0xff0f] |= 16;
	}

	extButtons = (gbJoymask[0] >> 18);
	speedup	   = (extButtons & 1) != 0;
}

static inline void gbBeforeEmulation()
{
	if (newFrame)
	{
		CallRegisteredLuaFunctions(LUACALL_BEFOREEMULATION);

		gbGetUserInput();

		if (gbRom[0x147] == 0x22)
		{
			//systemUpdateMotionSensor();
		}

		VBAMovieResetIfRequested();

		newFrame = false;
	}
}

static inline void gbFrameBoundaryWork()
{
	if (!gbSgbMask)
	{
		if (gbBorderOn)
			gbSgbRenderBorder();
	}

	systemFrameBoundaryWork();
}

void gbEmulate(int ticksToStop)
{
	gbBeforeEmulation();

	gbRegister tempRegister;
	u8		   tempValue;
	s8		   offset;

	gbClockTicks = 0;
	gbDmaTicks = 0;

	int	 opcode1 = 0;
	int	 opcode2 = 0;
	bool execute = false;

	for (;;)
	{
#ifndef FINAL_VERSION
		if (systemDebug)
		{
			if (!(IFF & 0x80))
			{
				if (systemDebug > 1)
				{
					log("PC=%04x AF=%04x BC=%04x DE=%04x HL=%04x SP=%04x I=%04x\n",
					    PC.W, AF.W, BC.W, DE.W, HL.W, SP.W, IFF);
				}
				else
				{
					log("PC=%04x I=%02x\n", PC.W, IFF);
				}
			}
		}
#endif

		u16 oldPCW = PC.W;

		if (IFF & 0x80)
		{
			if (register_LCDC & 0x80)
			{
				gbClockTicks = gbLcdTicks;
			}
			else
				gbClockTicks = 1000;

			gbClockTicks = gbGetNextEvent(gbClockTicks);

			/*if(gbLcdTicksDelayed < gbClockTicks)
			   gbClockTicks = gbLcdTicksDelayed;

			   if(gbLcdLYIncrementTicksDelayed < gbClockTicks)
			   gbClockTicks = gbLcdLYIncrementTicksDelayed;

			   if(gbLcdLYIncrementTicks < gbClockTicks)
			   gbClockTicks = gbLcdLYIncrementTicks;

			   if(gbSerialOn && (gbSerialTicks < gbClockTicks))
			   gbClockTicks = gbSerialTicks;

			   if(gbTimerOn && (((gbInternalTimer) & gbTimerMask[gbTimerMode])+1 < gbClockTicks))
			   gbClockTicks = ((gbInternalTimer) & gbTimerMask[gbTimerMode])+1;

			   if(soundTicks && (soundTicks < gbClockTicks))
			   gbClockTicks = soundTicks;

			   if ((gbClockTicks<=0) || (gbInterruptWait))
			    gbClockTicks = 1;*/
		}
		else
		{
			// First we apply the gbClockTicks, then we execute the opcodes.
			execute = true;

			register int opcode;
			opcode2 = opcode1 = opcode = gbReadOpcode(PC.W);
			CallRegisteredLuaMemHook(PC.W, 1, opcode, LUAMEMHOOK_EXEC);
			PC.W++;

			// If HALT state was launched while IME = 0 and (register_IF & register_IE & 0x1F),
			// PC.W is not incremented for the first byte of the next instruction.
			if (IFF & 2)
			{
				PC.W--;
				IFF &= ~2;
			}

			gbClockTicks = gbCycles[opcode];

			switch (opcode)
			{
			case 0xCB:
				// extended opcode
				opcode2 = opcode = gbReadOpcode(PC.W);
				CallRegisteredLuaMemHook(PC.W, 1, opcode, LUAMEMHOOK_EXEC);	// is this desired?
				PC.W++;
				gbClockTicks = gbCyclesCB[opcode];
				break;
			}
			gbOldClockTicks = gbClockTicks - 1;
			gbIntBreak		= 1;
		}

		if (!emulating)
			break;

		// For 'breakpoint' support (opcode 0xFC is considered as a breakpoint)
		if ((gbClockTicks == 0) && execute)
		{
			PC.W = oldPCW;
			return;
		}

		if (!(IFF & 0x80))
			gbClockTicks = 1;

gbRedoLoop:

		if (gbInterruptWait)
			gbInterruptWait = 0;
		else
			gbInterruptLaunched = 0;

		// Used for the EI/DI instruction's delay.
		if (IFF & 0x38)
		{
			int tempIFF = (IFF >> 4) & 3;

			if (tempIFF <= gbClockTicks)
			{
				tempIFF = 0;
				IFF	   |= 1;
			}
			else
				tempIFF -= gbClockTicks;
			IFF = (IFF & 0xCF) | (tempIFF << 4);

			if (IFF & 0x08)
				IFF &= 0x82;
		}

		if (register_LCDCBusy)
		{
			register_LCDCBusy -= gbClockTicks;
			if (register_LCDCBusy < 0)
				register_LCDCBusy = 0;
		}

		if (gbSgbMode)
		{
			if (gbSgbPacketTimeout)
			{
				gbSgbPacketTimeout -= gbClockTicks;

				if (gbSgbPacketTimeout <= 0)
					gbSgbResetPacketState();
			}
		}

		ticksToStop -= gbClockTicks;

		// DIV register emulation
		gbDivTicks -= gbClockTicks;
		while (gbDivTicks <= 0)
		{
			gbMemory[0xff04] = ++register_DIV;
			gbDivTicks		+= GBDIV_CLOCK_TICKS;
		}

		if (register_LCDC & 0x80)
		{
			// LCD stuff

			gbLcdTicks		  -= gbClockTicks;
			gbLcdTicksDelayed -= gbClockTicks;
			gbLcdLYIncrementTicks		 -= gbClockTicks;
			gbLcdLYIncrementTicksDelayed -= gbClockTicks;

			// our counters are off, see what we need to do

			// This looks (and kinda is) complicated, however this
			// is the only way I found to emulate properly the way
			// the real hardware operates...
			while (((gbLcdTicks <= 0) && (!gbLCDChangeHappened)) ||
			       ((gbLcdTicksDelayed <= 0) && (gbLCDChangeHappened)) ||
			       ((gbLcdLYIncrementTicks <= 0) && (!gbLYChangeHappened)) ||
			       ((gbLcdLYIncrementTicksDelayed <= 0) && (gbLYChangeHappened)))
			{
				if ((gbLcdLYIncrementTicks <= 0) && (!gbLYChangeHappened))
				{
					gbLYChangeHappened = true;
					gbMemory[0xff44]   = register_LY = (register_LY + 1) % 154;

					if (register_LY == 0x91)
					{
						/* if (IFF & 0x80)
						   gbScreenOn = !gbScreenOn;
						   else*/
						if (register_LCDC & 0x80)
							gbScreenOn = true;
					}

					gbLcdLYIncrementTicks += GBLY_INCREMENT_CLOCK_TICKS;

					if (gbLcdMode == 1)
					{
						if (register_LY == 153)
							gbLcdLYIncrementTicks -= GBLY_INCREMENT_CLOCK_TICKS - gbLine99Ticks;
						else if (register_LY == 0)
							gbLcdLYIncrementTicks += GBLY_INCREMENT_CLOCK_TICKS - gbLine99Ticks;
					}

					// GB only 'bug' : Halt state is broken one tick before LY==LYC interrupt
					// is reflected in the registers.
					if ((gbHardware & 5) && (IFF & 0x80) && (register_LY == register_LYC)
					    && (register_STAT & 0x40) && (register_LY != 0))
					{
						if (!((gbLcdModeDelayed != 1) && (register_LY == 0)))
						{
							gbInt48Signal &= ~9;
							gbCompareLYToLYC();
							gbLYChangeHappened = false;
							gbMemory[0xff41]   = register_STAT;
							gbMemory[0xff0f]   = register_IF;
						}

						gbLcdLYIncrementTicksDelayed += GBLY_INCREMENT_CLOCK_TICKS - gbLine99Ticks + 1;
					}
				}

				if ((gbLcdTicks <= 0) && (!gbLCDChangeHappened))
				{
					gbLCDChangeHappened = true;

					switch (gbLcdMode)
					{
					case 0:
					{
						// H-Blank
						// check if we reached the V-Blank period
						if (register_LY == 144)
						{
							// Yes, V-Blank
							// set the LY increment counter
							if (gbHardware & 0x5)
							{
								register_IF |= 1; // V-Blank interrupt
							}

							gbInt48Signal &= ~6;
							if (register_STAT & 0x10)
							{
								// send LCD interrupt only if no interrupt 48h signal...
								if ((!(gbInt48Signal & 1)) && ((!(gbInt48Signal & 8)) || (gbHardware & 0x0a)))
								{
									register_IF			|= 2;
									gbInterruptLaunched |= 2;
									if (gbHardware & 0xa)
										gbInterruptWait = 1;
								}
								gbInt48Signal |= 2;
							}
							gbInt48Signal &= ~1;

							gbLcdTicks += GBLCD_MODE_1_CLOCK_TICKS;
							gbLcdMode	= 1;
						}
						else
						{
							// go the the OAM being accessed mode
							gbLcdTicks += GBLCD_MODE_2_CLOCK_TICKS;
							gbLcdMode	= 2;

							gbInt48Signal &= ~6;
							if (register_STAT & 0x20)
							{
								// send LCD interrupt only if no interrupt 48h signal...
								if (!gbInt48Signal)
								{
									register_IF			|= 2;
									gbInterruptLaunched |= 2;
								}
								gbInt48Signal |= 4;
							}
							gbInt48Signal &= ~1;
						}
					}
					break;
					case 1:
					{
						// V-Blank
						// next mode is OAM being accessed mode
						gbInt48Signal &= ~5;
						if (register_STAT & 0x20)
						{
							// send LCD interrupt only if no interrupt 48h signal...
							if (!gbInt48Signal)
							{
								register_IF			|= 2;
								gbInterruptLaunched |= 2;
								if ((gbHardware & 0xa) && (IFF & 0x80))
									gbInterruptWait = 1;
							}
							gbInt48Signal |= 4;
						}
						gbInt48Signal &= ~2;

						gbLcdTicks += GBLCD_MODE_2_CLOCK_TICKS;

						gbLcdMode	= 2;
						register_LY = 0x00;
					}
					break;
					case 2:
					{
						// OAM being accessed mode
						// next mode is OAM and VRAM in use
						if ((gbScreenOn) && (register_LCDC & 0x80))
						{
							gbDrawSprites(false);

							// Used to add a one tick delay when a window line is drawn.
							//(fixes a part of Carmaggedon problem)

							if ((register_LCDC & 0x01 || gbCgbMode) && (register_LCDC & 0x20))
							{
								int tempinUseRegister_WY = inUseRegister_WY;
								int tempgbWindowLine = gbWindowLine;

								if (tempgbWindowLine < 0)
								{
									tempinUseRegister_WY = register_WY;
									if (register_LY > tempinUseRegister_WY)
										tempgbWindowLine = 146;
									else
										tempgbWindowLine = 0;
								}

								if (register_LY >= tempinUseRegister_WY)
								{
									int wx = inUseRegister_WX - 7;
									if (wx < 0)
										wx = 0;

									if (wx <= 159 && tempgbWindowLine <= 143)
										if (gbSpeed)
											for (int i = wx; i < 300; i++)
												gbSpritesTicks[i] += 3;
										else
											for (int i = wx; i < 300; i++)
												gbSpritesTicks[i] += 1;
								}
							}
						}

						gbInt48Signal &= ~7;

						gbLcdTicks += GBLCD_MODE_3_CLOCK_TICKS + gbSpritesTicks[299];
						gbLcdMode	= 3;
					}
					break;
					case 3:
					{
						// OAM and VRAM in use
						// next mode is H-Blank

						gbInt48Signal &= ~7;
						if (register_STAT & 0x08)
						{
							// send LCD interrupt only if no interrupt 48h signal...
							if (!(gbInt48Signal & 8))
							{
								register_IF |= 2;
								if ((gbHardware & 0xa) && (IFF & 0x80))
									gbInterruptWait = 1;
							}
							gbInt48Signal |= 1;
						}

						gbLcdTicks += GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299];

						gbLcdMode = 0;

						// No HDMA during HALT !
						if (gbHdmaOn && (!(IFF & 0x80) || (register_IE & register_IF & 0x1f)))
						{
							gbDoHdma();
						}
					}
					break;
					}
				}

				if ((gbLcdTicksDelayed <= 0) && (gbLCDChangeHappened))
				{
					//gbLcdTicksDelayed = gbLcdTicks+1;
					gbLCDChangeHappened = false;
					switch (gbLcdModeDelayed)
					{
					case 0:
					{
						// H-Blank

						memset(gbSCYLine, gbSCYLine[299], sizeof(gbSCYLine));
						memset(gbSCXLine, gbSCXLine[299], sizeof(gbSCXLine));
						memset(gbBgpLine, gbBgpLine[299], sizeof(gbBgpLine));
						memset(gbObp0Line, gbObp0Line[299], sizeof(gbObp0Line));
						memset(gbObp1Line, gbObp1Line[299], sizeof(gbObp1Line));
						memset(gbSpritesTicks, gbSpritesTicks[299], sizeof(gbSpritesTicks));

						// check if we reached the V-Blank period
						if (register_LY == 144)
						{
							// Yes, V-Blank
							// set the LY increment counter

							if (register_LCDC & 0x80)
							{
								if (gbHardware & 0xa)
								{
									register_IF			|= 1; // V-Blank interrupt
									gbInterruptLaunched |= 1;
								}
							}

							gbLcdTicksDelayed += GBLCD_MODE_1_CLOCK_TICKS;
							gbLcdModeDelayed   = 1;

							gbFrameBoundaryWork();
						}
						else
						{
							// go the the OAM being accessed mode
							gbLcdTicksDelayed += GBLCD_MODE_2_CLOCK_TICKS;
							gbLcdModeDelayed   = 2;
							gbInt48Signal	  &= ~3;
						}
					}
					break;
					case 1:
					{
						// V-Blank
						// next mode is OAM being accessed mode

						// gbScreenOn = true;

						gbLcdTicksDelayed += GBLCD_MODE_2_CLOCK_TICKS;
						gbLcdModeDelayed   = 2;

						// reset the window line
						gbWindowLine = -1;
					}
					break;
					case 2:
					{
						// OAM being accessed mode
						// next mode is OAM and VRAM in use
						gbCompareLYToWY();

						gbLcdTicksDelayed += GBLCD_MODE_3_CLOCK_TICKS + gbSpritesTicks[299];
						gbLcdModeDelayed   = 3;
					}
					break;
					case 3:
					{
						// OAM and VRAM in use
						// next mode is H-Blank
						if ((register_LY < 144) && (register_LCDC & 0x80) && gbScreenOn)
						{
							if (!gbSgbMask)
							{
								if (!gbBlackScreen)
								{
									gbRenderLine();
									gbDrawSprites(true);
								}

								if (systemFrameDrawingRequired())
								{
									if (gbBlackScreen)
									{
										u16 color = gbColorOption ? gbColorFilter[0] : 0;
										if (!gbCgbMode)
											color = gbColorOption ? gbColorFilter[gbPalette[3] & 0x7FFF] : gbPalette[3] & 0x7FFF;
										for (int i = 0; i < 160; i++)
										{
											gbLineMix[i]	= color;
											gbLineBuffer[i] = 0;
										}
									}
									gbDrawPixLine();
								}
							}
						}
						gbLcdTicksDelayed += GBLCD_MODE_0_CLOCK_TICKS - gbSpritesTicks[299];
						gbLcdModeDelayed   = 0;

						inUseRegister_WX = register_WX;
					}
					break;
					}
				}

				if ((gbLcdLYIncrementTicksDelayed <= 0) && (gbLYChangeHappened))
				{
					gbLYChangeHappened = false;

					if (!((gbLcdMode != 1) && (register_LY == 0)))
					{
						{
							gbInt48Signal &= ~8;
							gbCompareLYToLYC();
							if ((gbInt48Signal == 8) && (!((register_LY == 0) && (gbHardware & 1))))
								gbInterruptLaunched |= 2;
							if ((gbHardware & (gbSpeed ? 8 : 2)) && (register_LY == 0) && ((register_STAT & 0x44) == 0x44) &&
							    (gbLcdLYIncrementTicksDelayed == 0))
							{
								gbInterruptWait = 1;
							}
						}
					}
					gbLcdLYIncrementTicksDelayed += GBLY_INCREMENT_CLOCK_TICKS;

					if (gbLcdModeDelayed == 1)
					{
						if (register_LY == 153)
							gbLcdLYIncrementTicksDelayed -= GBLY_INCREMENT_CLOCK_TICKS - gbLine99Ticks;
						else if (register_LY == 0)
							gbLcdLYIncrementTicksDelayed += GBLY_INCREMENT_CLOCK_TICKS - gbLine99Ticks;
					}
					gbMemory[0xff0f] = register_IF;
					gbMemory[0xff41] = register_STAT;
				}
			}
			gbMemory[0xff0f] = register_IF;
			gbMemory[0xff41] = register_STAT = (register_STAT & 0xfc) | gbLcdModeDelayed;
		}
		else
		{
			// Used to update the screen with white lines when it's off.
			// (it looks strange, but it's kinda accurate :p)
			// You can try the Mario Demo Vx.x for exemple
			// (check the bottom 2 lines while moving)
			if (!gbWhiteScreen)
			{
				gbScreenTicks		  -= gbClockTicks;
				gbLcdLYIncrementTicks -= gbClockTicks;
				while (gbLcdLYIncrementTicks <= 0)
				{
					register_LY = ((register_LY + 1) % 154);
					gbLcdLYIncrementTicks += GBLY_INCREMENT_CLOCK_TICKS;
				}
				if (gbScreenTicks <= 0)
				{
					gbWhiteScreen = 1;
					u8 register_LYLcdOff = ((register_LY + 154) % 154);
					for (register_LY = 0; register_LY <=  0x90; register_LY++)
					{
						u16 color = gbColorOption ? gbColorFilter[0x7FFF] : 0x7FFF;
						if (!gbCgbMode)
							color = gbColorOption ? gbColorFilter[gbPalette[0] & 0x7FFF] : gbPalette[0] & 0x7FFF;
						for (int i = 0; i < 160; i++)
						{
							gbLineMix[i]	= color;
							gbLineBuffer[i] = 0;
						}
						gbDrawPixLine();
					}
					register_LY = register_LYLcdOff;
				}
			}

			if (gbWhiteScreen)
			{
				gbLcdLYIncrementTicks -= gbClockTicks;

				while (gbLcdLYIncrementTicks <= 0)
				{
					register_LY = ((register_LY + 1) % 154);
					gbLcdLYIncrementTicks += GBLY_INCREMENT_CLOCK_TICKS;
					if (register_LY < 144)
					{
						u16 color = gbColorOption ? gbColorFilter[0x7FFF] : 0x7FFF;
						if (!gbCgbMode)
							color = gbColorOption ? gbColorFilter[gbPalette[0] & 0x7FFF] : gbPalette[0] & 0x7FFF;
						for (int i = 0; i < 160; i++)
						{
							gbLineMix[i]	= color;
							gbLineBuffer[i] = 0;
						}
						gbDrawPixLine();
					}
					else if (register_LY == 144)
					{
						gbBeforeEmulation();
						gbFrameBoundaryWork();
					}
				}
			}
		}

		gbMemory[0xff41] = register_STAT;

		// serial emulation
		if (gbSerialOn)
		{
#ifdef OLD_GB_LINK
			if (linkConnected)
			{
				gbSerialTicks -= gbClockTicks;

				while (gbSerialTicks <= 0)
				{
					// increment number of shifted bits
					gbSerialBits++;
					linkProc();
					if (gbSerialOn && (gbMemory[0xff02] & 1))
					{
						if (gbSerialBits == 8)
						{
							gbSerialBits	  = 0;
							gbMemory[0xff01]  = 0xff;
							gbMemory[0xff02] &= 0x7f;
							gbSerialOn		  = 0;
							gbMemory[0xff0f]  = register_IF |= 8;
							gbSerialTicks	  = 0;
						}
					}
					gbSerialTicks += GBSERIAL_CLOCK_TICKS;
				}
			}
			else
			{
#endif
			if (gbMemory[0xff02] & 1)
			{
				gbSerialTicks -= gbClockTicks;

				// overflow
				while (gbSerialTicks <= 0)
				{
					// shift serial byte to right and put a 1 bit in its place
					//      gbMemory[0xff01] = 0x80 | (gbMemory[0xff01]>>1);
					// increment number of shifted bits
					gbSerialBits++;
					if (gbSerialBits == 8)
					{
						// end of transmission
						if (gbSerialFunction) // external device
							gbMemory[0xff01] = gbSerialFunction(gbMemory[0xff01]);
						else
							gbMemory[0xff01] = 0xff;
						gbSerialTicks	  = 0;
						gbMemory[0xff02] &= 0x7f;
						gbSerialOn		  = 0;
						gbMemory[0xff0f]  = register_IF |= 8;
						gbSerialBits	  = 0;
					}
					else
						gbSerialTicks += GBSERIAL_CLOCK_TICKS;
				}
			}
#ifdef OLD_GB_LINK
		}
#endif
		}

		soundTicks -= gbClockTicks;
		if (!gbSpeed)
			soundTicks -= gbClockTicks;

		while (soundTicks < 0)
		{
			gbSoundTick();
			soundTicks += soundTickStep;
		}

		// timer emulation
		if (gbTimerOn)
		{
			gbTimerTicks = ((gbInternalTimer) & gbTimerMask[gbTimerMode]) + 1 - gbClockTicks;

			while (gbTimerTicks <= 0)
			{
				register_TIMA++;
				// timer overflow!
				if ((register_TIMA & 0xff) == 0)
				{
					// reload timer modulo
					register_TIMA = register_TMA;
					// flag interrupt
					gbMemory[0xff0f] = register_IF |= 4;
				}
				gbTimerTicks += gbTimerClockTicks;
			}
			gbTimerOnChange	  = false;
			gbTimerModeChange = false;

			gbMemory[0xff05] = register_TIMA;
		}

		gbInternalTimer -= gbClockTicks;
		while (gbInternalTimer < 0)
			gbInternalTimer += 0x100;

		gbClockTicks = 0;

		if (gbIntBreak == 1)
		{
			gbIntBreak = 0;
			if ((register_IE & register_IF & gbInterruptLaunched & 0x3) &&
			    ((IFF & 0x81) == 1) && (!gbInterruptWait) && (execute))
			{
				gbIntBreak		= 2;
				PC.W			= oldPCW;
				execute			= false;
				gbOldClockTicks = 0;
			}
			if (gbOldClockTicks)
			{
				gbClockTicks		= gbOldClockTicks;
				gbOldClockTicks = 0;
				goto gbRedoLoop;
			}
		}

		// Executes the opcode(s), and apply the instruction's remaining gbClockTicks (if any).
		if (execute)
		{
#include "gbCodes.h"

			execute = false;

			if (gbClockTicks)
			{
				gbDmaTicks += gbClockTicks;
				gbClockTicks	= 0;
			}
		}

		if (gbDmaTicks)
		{
			gbClockTicks = gbGetNextEvent(gbDmaTicks);

			if (gbClockTicks <= gbDmaTicks)
				gbDmaTicks -= gbClockTicks;
			else
			{
				gbClockTicks = gbDmaTicks;
				gbDmaTicks = 0;
			}

			goto gbRedoLoop;
		}

		// Remove the 'if an IE is pending' flag if IE has finished being executed.
		if ((IFF & 0x40) && !(IFF & 0x30))
			IFF &= 0x81;

		if ((register_IE & register_IF & 0x1f) && (IFF & 0x81) && (!gbInterruptWait))
		{
			if (IFF & 1)
			{
				// Add 5 ticks for the interrupt execution time
				gbDmaTicks +=  5;

				if (gbIntBreak == 2)
				{
					gbDmaTicks--;
					gbIntBreak = 0;
				}

				if (register_IF & register_IE & 1)
					gbVblank_interrupt();
				else if (register_IF & register_IE & 2)
					gbLcd_interrupt();
				else if (register_IF & register_IE & 4)
					gbTimer_interrupt();
				else if (register_IF & register_IE & 8)
					gbSerial_interrupt();
				else if (register_IF & register_IE & 16)
					gbJoypad_interrupt();
			}

			IFF &= ~0x81;
		}

		if (IFF & 0x08)
			IFF &= ~0x79;

		// Used to apply the interrupt's execution time.
		if (gbDmaTicks)
		{
			gbClockTicks = gbGetNextEvent(gbDmaTicks);

			if (gbClockTicks <= gbDmaTicks)
				gbDmaTicks -= gbClockTicks;
			else
			{
				gbClockTicks = gbDmaTicks;
				gbDmaTicks = 0;
			}
			goto gbRedoLoop;
		}

		gbBlackScreen = false;

		if (useOldFrameTiming)
		{
			// old timing code
			if (ticksToStop > 0)
				continue;

			// v20 frame timing hack
			if (!(register_LCDC & 0x80))
			{
				++stopCounter;
				if (gbV20GBFrameTimingHackTemp && stopCounter % 64 == 0)
				{
					gbBeforeEmulation();
					gbFrameBoundaryWork();
				}
			}

			break;
		}

		if (newFrame)
		{
			break;
		}
	}
}

struct EmulatedSystem GBSystem =
{
	// emuMain
	gbEmulate,
	// emuReset
	gbReset,
	// emuCleanUp
	gbCleanUp,
	// emuReadBattery
	gbReadBatteryFile,
	// emuWriteBattery
	gbWriteBatteryFile,
	// emuReadBatteryFromStream
	gbReadBatteryFromStream,
	// emuWriteBatteryToStream
	gbWriteBatteryToStream,
	// emuReadState
	gbReadSaveState,
	// emuWriteState
	gbWriteSaveState,
	// emuReadStateFromStream
	gbReadSaveStateFromStream,
	// emuWriteStateToStream
	gbWriteSaveStateToStream,
	// emuReadMemState
	gbReadMemSaveState,
	// emuWriteMemState
	gbWriteMemSaveState,
	// emuWritePNG
	gbWritePNGFile,
	// emuWriteBMP
	gbWriteBMPFile,
	// emuUpdateCPSR
	NULL,
	// emuHasDebugger
	false,
	// emuCount
#ifdef FINAL_VERSION
	70000 / 4,
#else
	7000,
#endif
};
